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 class DCNetwork(Containernet
):
52 Wraps the original Mininet/Containernet class and provides
53 methods to add data centers, switches, etc.
55 This class is used by topology definition scripts.
58 def __init__(self
, controller
=RemoteController
, monitor
=False,
59 enable_learning
=False, # learning switch behavior of the default ovs switches icw Ryu controller can be turned off/on, neede for E-LAN functionality
60 dc_emulation_max_cpu
=1.0, # fraction of overall CPU time for emulation
61 dc_emulation_max_mem
=512, # emulation max mem in MB
64 Create an extended version of a Containernet network
65 :param dc_emulation_max_cpu: max. CPU time used by containers in data centers
66 :param kwargs: path through for Mininet parameters
71 self
.ryu_process
= None
72 #list of deployed nsds.E_Lines and E_LANs (uploaded from the dummy gatekeeper)
73 self
.deployed_nsds
= []
74 self
.deployed_elines
= []
75 self
.deployed_elans
= []
76 self
.installed_chains
= []
79 # always cleanup environment before we start the emulator
83 # call original Docker.__init__ and setup default controller
84 Containernet
.__init
__(
85 self
, switch
=OVSKernelSwitch
, controller
=controller
, **kwargs
)
87 # default switch configuration
88 enable_ryu_learning
= False
90 self
.failMode
= 'standalone'
91 enable_ryu_learning
= True
93 self
.failMode
= 'secure'
96 if controller
== RemoteController
:
97 # start Ryu controller
98 self
.startRyu(learning_switch
=enable_ryu_learning
)
100 # add the specified controller
101 self
.addController('c0', controller
=controller
)
103 # graph of the complete DC network
104 self
.DCNetwork_graph
= nx
.MultiDiGraph()
106 # initialize pool of vlan tags to setup the SDN paths
107 self
.vlans
= range(4096)[::-1]
109 # link to Ryu REST_API
112 self
.ryu_REST_api
= 'http://{0}:{1}'.format(ryu_ip
, ryu_port
)
113 self
.RyuSession
= requests
.Session()
117 self
.monitor_agent
= DCNetworkMonitor(self
)
119 self
.monitor_agent
= None
121 # initialize resource model registrar
122 self
.rm_registrar
= ResourceModelRegistrar(
123 dc_emulation_max_cpu
, dc_emulation_max_mem
)
125 def addDatacenter(self
, label
, metadata
={}, resource_log_path
=None):
127 Create and add a logical cloud data center to the network.
129 if label
in self
.dcs
:
130 raise Exception("Data center label already exists: %s" % label
)
131 dc
= Datacenter(label
, metadata
=metadata
, resource_log_path
=resource_log_path
)
132 dc
.net
= self
# set reference to network
134 dc
.create() # finally create the data center in our Mininet instance
135 LOG
.info("added data center: %s" % label
)
138 def addLink(self
, node1
, node2
, **params
):
140 Able to handle Datacenter objects as link
143 assert node1
is not None
144 assert node2
is not None
146 # ensure type of node1
147 if isinstance( node1
, basestring
):
148 if node1
in self
.dcs
:
149 node1
= self
.dcs
[node1
].switch
150 if isinstance( node1
, Datacenter
):
152 # ensure type of node2
153 if isinstance( node2
, basestring
):
154 if node2
in self
.dcs
:
155 node2
= self
.dcs
[node2
].switch
156 if isinstance( node2
, Datacenter
):
158 # try to give containers a default IP
159 if isinstance( node1
, Docker
):
160 if "params1" not in params
:
161 params
["params1"] = {}
162 if "ip" not in params
["params1"]:
163 params
["params1"]["ip"] = self
.getNextIp()
164 if isinstance( node2
, Docker
):
165 if "params2" not in params
:
166 params
["params2"] = {}
167 if "ip" not in params
["params2"]:
168 params
["params2"]["ip"] = self
.getNextIp()
169 # ensure that we allow TCLinks between data centers
170 # TODO this is not optimal, we use cls=Link for containers and TCLink for data centers
171 # see Containernet issue: https://github.com/mpeuster/containernet/issues/3
172 if "cls" not in params
:
173 params
["cls"] = TCLink
175 link
= Containernet
.addLink(self
, node1
, node2
, **params
)
177 # try to give container interfaces a default id
178 node1_port_id
= node1
.ports
[link
.intf1
]
179 if isinstance(node1
, Docker
):
180 if "id" in params
["params1"]:
181 node1_port_id
= params
["params1"]["id"]
182 node1_port_name
= link
.intf1
.name
184 node2_port_id
= node2
.ports
[link
.intf2
]
185 if isinstance(node2
, Docker
):
186 if "id" in params
["params2"]:
187 node2_port_id
= params
["params2"]["id"]
188 node2_port_name
= link
.intf2
.name
191 # add edge and assigned port number to graph in both directions between node1 and node2
192 # port_id: id given in descriptor (if available, otherwise same as port)
193 # port: portnumber assigned by Containernet
196 # possible weight metrics allowed by TClink class:
197 weight_metrics
= ['bw', 'delay', 'jitter', 'loss']
198 edge_attributes
= [p
for p
in params
if p
in weight_metrics
]
199 for attr
in edge_attributes
:
200 # if delay: strip ms (need number as weight in graph)
201 match
= re
.search('([0-9]*\.?[0-9]+)', params
[attr
])
203 attr_number
= match
.group(1)
206 attr_dict
[attr
] = attr_number
209 attr_dict2
= {'src_port_id': node1_port_id
, 'src_port_nr': node1
.ports
[link
.intf1
],
210 'src_port_name': node1_port_name
,
211 'dst_port_id': node2_port_id
, 'dst_port_nr': node2
.ports
[link
.intf2
],
212 'dst_port_name': node2_port_name
}
213 attr_dict2
.update(attr_dict
)
214 self
.DCNetwork_graph
.add_edge(node1
.name
, node2
.name
, attr_dict
=attr_dict2
)
216 attr_dict2
= {'src_port_id': node2_port_id
, 'src_port_nr': node2
.ports
[link
.intf2
],
217 'src_port_name': node2_port_name
,
218 'dst_port_id': node1_port_id
, 'dst_port_nr': node1
.ports
[link
.intf1
],
219 'dst_port_name': node1_port_name
}
220 attr_dict2
.update(attr_dict
)
221 self
.DCNetwork_graph
.add_edge(node2
.name
, node1
.name
, attr_dict
=attr_dict2
)
223 LOG
.debug("addLink: n1={0} intf1={1} -- n2={2} intf2={3}".format(
224 str(node1
),node1_port_name
, str(node2
), node2_port_name
))
228 def addDocker( self
, label
, **params
):
230 Wrapper for addDocker method to use custom container class.
232 self
.DCNetwork_graph
.add_node(label
)
233 return Containernet
.addDocker(self
, label
, cls
=EmulatorCompute
, **params
)
235 def removeDocker( self
, label
, **params
):
237 Wrapper for removeDocker method to update graph.
239 self
.DCNetwork_graph
.remove_node(label
)
240 return Containernet
.removeDocker(self
, label
, **params
)
242 def addSwitch( self
, name
, add_to_graph
=True, **params
):
244 Wrapper for addSwitch method to store switch also in graph.
247 # add this switch to the global topology overview
249 self
.DCNetwork_graph
.add_node(name
)
251 # set the learning switch behavior
252 if 'failMode' in params
:
253 failMode
= params
['failMode']
255 failMode
= self
.failMode
257 s
= Containernet
.addSwitch(self
, name
, protocols
='OpenFlow10,OpenFlow12,OpenFlow13', failMode
=failMode
, **params
)
259 # set flow entry that enables learning switch behavior (needed to enable E-LAN functionality)
260 #LOG.info('failmode {0}'.format(failMode))
261 #if failMode == 'standalone' :
262 # LOG.info('add NORMAL')
263 # s.dpctl('add-flow', 'actions=NORMAL')
267 def getAllContainers(self
):
269 Returns a list with all containers within all data centers.
272 for dc
in self
.dcs
.itervalues():
273 all_containers
+= dc
.listCompute()
274 return all_containers
278 for dc
in self
.dcs
.itervalues():
280 Containernet
.start(self
)
284 # stop the monitor agent
285 if self
.monitor_agent
is not None:
286 self
.monitor_agent
.stop()
289 Containernet
.stop(self
)
291 # stop Ryu controller
298 def setLAN(self
, vnf_list
):
300 setup an E-LAN network by assigning the same VLAN tag to each DC interface of the VNFs in the E-LAN
302 :param vnf_list: names of the VNFs in this E-LAN [{name:,interface:},...]
307 src_sw_inport_name
= None
309 # get a vlan tag for this E-LAN
310 vlan
= self
.vlans
.pop()
313 vnf_src_name
= vnf
['name']
314 vnf_src_interface
= vnf
['interface']
316 # check if port is specified (vnf:port)
317 if vnf_src_interface
is None:
318 # take first interface by default
319 connected_sw
= self
.DCNetwork_graph
.neighbors(vnf_src_name
)[0]
320 link_dict
= self
.DCNetwork_graph
[vnf_src_name
][connected_sw
]
321 vnf_src_interface
= link_dict
[0]['src_port_id']
323 for connected_sw
in self
.DCNetwork_graph
.neighbors(vnf_src_name
):
324 link_dict
= self
.DCNetwork_graph
[vnf_src_name
][connected_sw
]
325 for link
in link_dict
:
326 if (link_dict
[link
]['src_port_id'] == vnf_src_interface
or
327 link_dict
[link
]['src_port_name'] == vnf_src_interface
): # Fix: we might also get interface names, e.g, from a son-emu-cli call
328 # found the right link and connected switch
329 src_sw
= connected_sw
330 src_sw_inport_nr
= link_dict
[link
]['dst_port_nr']
331 src_sw_inport_name
= link_dict
[link
]['dst_port_name']
334 # set the tag on the dc switch interface
335 LOG
.debug('set E-LAN: vnf name: {0} interface: {1} tag: {2}'.format(vnf_src_name
, vnf_src_interface
,vlan
))
336 switch_node
= self
.getNodeByName(src_sw
)
337 self
._set
_vlan
_tag
(switch_node
, src_sw_inport_name
, vlan
)
339 def _addMonitorFlow(self
, vnf_src_name
, vnf_dst_name
, vnf_src_interface
=None, vnf_dst_interface
=None,
344 src_sw_inport_name
= None
346 dst_sw_outport_nr
= 0
347 dst_sw_outport_name
= None
349 LOG
.debug("call AddMonitorFlow vnf_src_name=%r, vnf_src_interface=%r, vnf_dst_name=%r, vnf_dst_interface=%r",
350 vnf_src_name
, vnf_src_interface
, vnf_dst_name
, vnf_dst_interface
)
352 #check if port is specified (vnf:port)
353 if vnf_src_interface
is None:
354 # take first interface by default
355 connected_sw
= self
.DCNetwork_graph
.neighbors(vnf_src_name
)[0]
356 link_dict
= self
.DCNetwork_graph
[vnf_src_name
][connected_sw
]
357 vnf_src_interface
= link_dict
[0]['src_port_id']
359 for connected_sw
in self
.DCNetwork_graph
.neighbors(vnf_src_name
):
360 link_dict
= self
.DCNetwork_graph
[vnf_src_name
][connected_sw
]
361 for link
in link_dict
:
362 if (link_dict
[link
]['src_port_id'] == vnf_src_interface
or
363 link_dict
[link
]['src_port_name'] == vnf_src_interface
): # Fix: we might also get interface names, e.g, from a son-emu-cli call
364 # found the right link and connected switch
365 src_sw
= connected_sw
366 src_sw_inport_nr
= link_dict
[link
]['dst_port_nr']
367 src_sw_inport_name
= link_dict
[link
]['dst_port_name']
370 if vnf_dst_interface
is None:
371 # take first interface by default
372 connected_sw
= self
.DCNetwork_graph
.neighbors(vnf_dst_name
)[0]
373 link_dict
= self
.DCNetwork_graph
[connected_sw
][vnf_dst_name
]
374 vnf_dst_interface
= link_dict
[0]['dst_port_id']
376 vnf_dst_name
= vnf_dst_name
.split(':')[0]
377 for connected_sw
in self
.DCNetwork_graph
.neighbors(vnf_dst_name
):
378 link_dict
= self
.DCNetwork_graph
[connected_sw
][vnf_dst_name
]
379 for link
in link_dict
:
380 if link_dict
[link
]['dst_port_id'] == vnf_dst_interface
or \
381 link_dict
[link
]['dst_port_name'] == vnf_dst_interface
: # Fix: we might also get interface names, e.g, from a son-emu-cli call
382 # found the right link and connected switch
383 dst_sw
= connected_sw
384 dst_sw_outport_nr
= link_dict
[link
]['src_port_nr']
385 dst_sw_outport_name
= link_dict
[link
]['src_port_name']
389 LOG
.exception('tag not valid: {0}'.format(tag
))
393 # returns the first found shortest path
394 # if all shortest paths are wanted, use: all_shortest_paths
395 path
= nx
.shortest_path(self
.DCNetwork_graph
, src_sw
, dst_sw
, weight
=kwargs
.get('weight'))
397 LOG
.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
398 vnf_src_name
, vnf_dst_name
, src_sw
, dst_sw
))
399 LOG
.debug("Graph nodes: %r" % self
.DCNetwork_graph
.nodes())
400 LOG
.debug("Graph edges: %r" % self
.DCNetwork_graph
.edges())
401 for e
, v
in self
.DCNetwork_graph
.edges():
402 LOG
.debug("%r" % self
.DCNetwork_graph
[e
][v
])
403 return "No path could be found between {0} and {1}".format(vnf_src_name
, vnf_dst_name
)
405 LOG
.info("Path between {0} and {1}: {2}".format(vnf_src_name
, vnf_dst_name
, path
))
408 switch_inport_nr
= src_sw_inport_nr
410 cmd
= kwargs
.get('cmd')
412 #iterate through the path to install the flow-entries
413 for i
in range(0,len(path
)):
414 current_node
= self
.getNodeByName(current_hop
)
416 if path
.index(current_hop
) < len(path
)-1:
417 next_hop
= path
[path
.index(current_hop
)+1]
420 next_hop
= vnf_dst_name
422 next_node
= self
.getNodeByName(next_hop
)
424 if next_hop
== vnf_dst_name
:
425 switch_outport_nr
= dst_sw_outport_nr
426 LOG
.info("end node reached: {0}".format(vnf_dst_name
))
427 elif not isinstance( next_node
, OVSSwitch
):
428 LOG
.info("Next node: {0} is not a switch".format(next_hop
))
429 return "Next node: {0} is not a switch".format(next_hop
)
431 # take first link between switches by default
433 switch_outport_nr
= self
.DCNetwork_graph
[current_hop
][next_hop
][index_edge_out
]['src_port_nr']
436 # set of entry via ovs-ofctl
437 if isinstance( current_node
, OVSSwitch
):
439 kwargs
['path'] = path
440 kwargs
['current_hop'] = current_hop
441 kwargs
['switch_inport_name'] = src_sw_inport_name
442 kwargs
['switch_outport_name'] = dst_sw_outport_name
443 kwargs
['skip_vlan_tag'] = True
445 monitor_placement
= kwargs
.get('monitor_placement')
446 # put monitor flow at the dst switch
448 if monitor_placement
== 'tx' and path
.index(current_hop
) == 0: # first node:
450 # put monitoring flow at the src switch
451 elif monitor_placement
== 'rx' and path
.index(current_hop
) == len(path
) - 1: # last node:
454 LOG
.exception('invalid monitor command: {0}'.format(monitor_placement
))
457 if self
.controller
== RemoteController
and insert_flow
:
458 ## set flow entry via ryu rest api
459 self
._set
_flow
_entry
_ryu
_rest
(current_node
, switch_inport_nr
, switch_outport_nr
, **kwargs
)
462 ## set flow entry via ovs-ofctl
463 self
._set
_flow
_entry
_dpctl
(current_node
, switch_inport_nr
, switch_outport_nr
, **kwargs
)
466 # take first link between switches by default
467 if isinstance( next_node
, OVSSwitch
):
468 switch_inport_nr
= self
.DCNetwork_graph
[current_hop
][next_hop
][0]['dst_port_nr']
469 current_hop
= next_hop
471 return "path {2} between {0} and {1}".format(vnf_src_name
, vnf_dst_name
, cmd
)
474 def setChain(self
, vnf_src_name
, vnf_dst_name
, vnf_src_interface
=None, vnf_dst_interface
=None, **kwargs
):
476 Chain 2 vnf interfaces together by installing the flowrules in the switches along their path.
477 Currently the path is found using the default networkx shortest path function.
478 Each chain gets a unique vlan id , so different chains wil not interfere.
480 :param vnf_src_name: vnf name (string)
481 :param vnf_dst_name: vnf name (string)
482 :param vnf_src_interface: source interface name (string)
483 :param vnf_dst_interface: destination interface name (string)
484 :param cmd: 'add-flow' (default) to add a chain, 'del-flows' to remove a chain
485 :param cookie: cookie for the installed flowrules (can be used later as identifier for a set of installed chains)
486 :param match: custom match entry to be added to the flowrules (default: only in_port and vlan tag)
487 :param priority: custom flowrule priority
488 :return: output log string
491 # special procedure for monitoring flows
492 if kwargs
.get('monitor'):
494 # check if chain already exists
495 found_chains
= [chain_dict
for chain_dict
in self
.installed_chains
if
496 (chain_dict
['vnf_src_name'] == vnf_src_name
and chain_dict
['vnf_src_interface'] == vnf_src_interface
497 and chain_dict
['vnf_dst_name'] == vnf_dst_name
and chain_dict
['vnf_dst_interface'] == vnf_dst_interface
)]
499 if len(found_chains
) > 0:
500 # this chain exists, so need an extra monitoring flow
501 # assume only 1 chain per vnf/interface pair
502 LOG
.debug('*** installing monitoring chain on top of pre-defined chain from {0}:{1} -> {2}:{3}'.
503 format(vnf_src_name
, vnf_src_interface
, vnf_dst_name
, vnf_dst_interface
))
504 tag
= found_chains
[0]['tag']
505 ret
= self
._addMonitorFlow
(vnf_src_name
, vnf_dst_name
, vnf_src_interface
, vnf_dst_interface
,
506 tag
=tag
, table_id
=0, **kwargs
)
509 # no chain existing (or E-LAN) -> install normal chain
510 LOG
.warning('*** installing monitoring chain without pre-defined chain from {0}:{1} -> {2}:{3}'.
511 format(vnf_src_name
, vnf_src_interface
, vnf_dst_name
, vnf_dst_interface
))
515 cmd
= kwargs
.get('cmd')
516 if cmd
== 'add-flow' or cmd
== 'del-flows':
517 ret
= self
._chainAddFlow
(vnf_src_name
, vnf_dst_name
, vnf_src_interface
, vnf_dst_interface
, **kwargs
)
518 if kwargs
.get('bidirectional'):
519 ret
= ret
+'\n' + self
._chainAddFlow
(vnf_dst_name
, vnf_src_name
, vnf_dst_interface
, vnf_src_interface
, **kwargs
)
522 ret
= "Command unknown"
527 def _chainAddFlow(self
, vnf_src_name
, vnf_dst_name
, vnf_src_interface
=None, vnf_dst_interface
=None, **kwargs
):
531 src_sw_inport_name
= None
533 dst_sw_outport_nr
= 0
534 dst_sw_outport_name
= None
536 LOG
.debug("call chainAddFlow vnf_src_name=%r, vnf_src_interface=%r, vnf_dst_name=%r, vnf_dst_interface=%r",
537 vnf_src_name
, vnf_src_interface
, vnf_dst_name
, vnf_dst_interface
)
539 #check if port is specified (vnf:port)
540 if vnf_src_interface
is None:
541 # take first interface by default
542 connected_sw
= self
.DCNetwork_graph
.neighbors(vnf_src_name
)[0]
543 link_dict
= self
.DCNetwork_graph
[vnf_src_name
][connected_sw
]
544 vnf_src_interface
= link_dict
[0]['src_port_id']
546 for connected_sw
in self
.DCNetwork_graph
.neighbors(vnf_src_name
):
547 link_dict
= self
.DCNetwork_graph
[vnf_src_name
][connected_sw
]
548 for link
in link_dict
:
549 if (link_dict
[link
]['src_port_id'] == vnf_src_interface
or
550 link_dict
[link
]['src_port_name'] == vnf_src_interface
): # Fix: we might also get interface names, e.g, from a son-emu-cli call
551 # found the right link and connected switch
552 src_sw
= connected_sw
553 src_sw_inport_nr
= link_dict
[link
]['dst_port_nr']
554 src_sw_inport_name
= link_dict
[link
]['dst_port_name']
557 if vnf_dst_interface
is None:
558 # take first interface by default
559 connected_sw
= self
.DCNetwork_graph
.neighbors(vnf_dst_name
)[0]
560 link_dict
= self
.DCNetwork_graph
[connected_sw
][vnf_dst_name
]
561 vnf_dst_interface
= link_dict
[0]['dst_port_id']
563 vnf_dst_name
= vnf_dst_name
.split(':')[0]
564 for connected_sw
in self
.DCNetwork_graph
.neighbors(vnf_dst_name
):
565 link_dict
= self
.DCNetwork_graph
[connected_sw
][vnf_dst_name
]
566 for link
in link_dict
:
567 if link_dict
[link
]['dst_port_id'] == vnf_dst_interface
or \
568 link_dict
[link
]['dst_port_name'] == vnf_dst_interface
: # Fix: we might also get interface names, e.g, from a son-emu-cli call
569 # found the right link and connected switch
570 dst_sw
= connected_sw
571 dst_sw_outport_nr
= link_dict
[link
]['src_port_nr']
572 dst_sw_outport_name
= link_dict
[link
]['src_port_name']
578 # returns the first found shortest path
579 # if all shortest paths are wanted, use: all_shortest_paths
580 path
= nx
.shortest_path(self
.DCNetwork_graph
, src_sw
, dst_sw
, weight
=kwargs
.get('weight'))
582 LOG
.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
583 vnf_src_name
, vnf_dst_name
, src_sw
, dst_sw
))
584 LOG
.debug("Graph nodes: %r" % self
.DCNetwork_graph
.nodes())
585 LOG
.debug("Graph edges: %r" % self
.DCNetwork_graph
.edges())
586 for e
, v
in self
.DCNetwork_graph
.edges():
587 LOG
.debug("%r" % self
.DCNetwork_graph
[e
][v
])
588 return "No path could be found between {0} and {1}".format(vnf_src_name
, vnf_dst_name
)
590 LOG
.info("Path between {0} and {1}: {2}".format(vnf_src_name
, vnf_dst_name
, path
))
593 switch_inport_nr
= src_sw_inport_nr
596 ## if path contains more than 1 switch
597 cmd
= kwargs
.get('cmd')
599 if cmd
== 'add-flow':
600 if kwargs
.get('tag'):
601 # use pre-defined tag
602 vlan
= kwargs
.get('tag')
604 vlan
= self
.vlans
.pop()
606 # store the used vlan tag to identify this chain
607 if not kwargs
.get('monitor'):
609 chain_dict
['vnf_src_name'] = vnf_src_name
610 chain_dict
['vnf_dst_name'] = vnf_dst_name
611 chain_dict
['vnf_src_interface'] = vnf_src_interface
612 chain_dict
['vnf_dst_interface'] = vnf_dst_interface
613 chain_dict
['tag'] = vlan
614 self
.installed_chains
.append(chain_dict
)
616 #iterate through the path to install the flow-entries
617 for i
in range(0,len(path
)):
618 current_node
= self
.getNodeByName(current_hop
)
620 if path
.index(current_hop
) < len(path
)-1:
621 next_hop
= path
[path
.index(current_hop
)+1]
624 next_hop
= vnf_dst_name
626 next_node
= self
.getNodeByName(next_hop
)
628 if next_hop
== vnf_dst_name
:
629 switch_outport_nr
= dst_sw_outport_nr
630 LOG
.info("end node reached: {0}".format(vnf_dst_name
))
631 elif not isinstance( next_node
, OVSSwitch
):
632 LOG
.info("Next node: {0} is not a switch".format(next_hop
))
633 return "Next node: {0} is not a switch".format(next_hop
)
635 # take first link between switches by default
637 switch_outport_nr
= self
.DCNetwork_graph
[current_hop
][next_hop
][index_edge_out
]['src_port_nr']
640 # set of entry via ovs-ofctl
641 if isinstance( current_node
, OVSSwitch
):
642 kwargs
['vlan'] = vlan
643 kwargs
['path'] = path
644 kwargs
['current_hop'] = current_hop
645 kwargs
['switch_inport_name'] = src_sw_inport_name
646 kwargs
['switch_outport_name'] = dst_sw_outport_name
648 if self
.controller
== RemoteController
:
649 ## set flow entry via ryu rest api
650 self
._set
_flow
_entry
_ryu
_rest
(current_node
, switch_inport_nr
, switch_outport_nr
, **kwargs
)
652 ## set flow entry via ovs-ofctl
653 self
._set
_flow
_entry
_dpctl
(current_node
, switch_inport_nr
, switch_outport_nr
, **kwargs
)
655 # take first link between switches by default
656 if isinstance( next_node
, OVSSwitch
):
657 switch_inport_nr
= self
.DCNetwork_graph
[current_hop
][next_hop
][0]['dst_port_nr']
658 current_hop
= next_hop
660 return "path {2} between {0} and {1}".format(vnf_src_name
, vnf_dst_name
, cmd
)
662 def _set_flow_entry_ryu_rest(self
, node
, switch_inport_nr
, switch_outport_nr
, **kwargs
):
663 match
= 'in_port=%s' % switch_inport_nr
665 cookie
= kwargs
.get('cookie')
666 match_input
= kwargs
.get('match')
667 cmd
= kwargs
.get('cmd')
668 path
= kwargs
.get('path')
669 current_hop
= kwargs
.get('current_hop')
670 vlan
= kwargs
.get('vlan')
671 priority
= kwargs
.get('priority')
672 # flag to not set the ovs port vlan tag
673 skip_vlan_tag
= kwargs
.get('skip_vlan_tag')
674 # table id to put this flowentry
675 table_id
= kwargs
.get('table_id')
681 match
= s
.join([match
, match_input
])
684 flow
['dpid'] = int(node
.dpid
, 16)
687 flow
['cookie'] = int(cookie
)
689 flow
['priority'] = int(priority
)
691 flow
['table_id'] = table_id
695 # possible Ryu actions, match fields:
696 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#add-a-flow-entry
697 if cmd
== 'add-flow':
698 prefix
= 'stats/flowentry/add'
700 if path
.index(current_hop
) == 0: # first node
701 # set vlan tag in ovs instance (to isolate E-LANs)
702 if not skip_vlan_tag
:
703 in_port_name
= kwargs
.get('switch_inport_name')
704 self
._set
_vlan
_tag
(node
, in_port_name
, vlan
)
705 # set vlan push action if more than 1 switch in the path
708 action
['type'] = 'PUSH_VLAN' # Push a new VLAN tag if a input frame is non-VLAN-tagged
709 action
['ethertype'] = 33024 # Ethertype 0x8100(=33024): IEEE 802.1Q VLAN-tagged frame
710 flow
['actions'].append(action
)
712 action
['type'] = 'SET_FIELD'
713 action
['field'] = 'vlan_vid'
714 action
['value'] = vlan
715 flow
['actions'].append(action
)
717 if path
.index(current_hop
) == len(path
) - 1: # last node
718 # set vlan tag in ovs instance (to isolate E-LANs)
719 if not skip_vlan_tag
:
720 out_port_name
= kwargs
.get('switch_outport_name')
721 self
._set
_vlan
_tag
(node
, out_port_name
, vlan
)
722 # set vlan pop action if more than 1 switch in the path
724 match
+= ',dl_vlan=%s' % vlan
726 action
['type'] = 'POP_VLAN'
727 flow
['actions'].append(action
)
729 if 0 < path
.index(current_hop
) < (len(path
) - 1): # middle nodes
730 match
+= ',dl_vlan=%s' % vlan
732 # output action must come last
734 action
['type'] = 'OUTPUT'
735 action
['port'] = switch_outport_nr
736 flow
['actions'].append(action
)
738 elif cmd
== 'del-flows':
739 prefix
= 'stats/flowentry/delete'
742 # TODO: add cookie_mask as argument
743 flow
['cookie_mask'] = int('0xffffffffffffffff', 16) # need full mask to match complete cookie
746 action
['type'] = 'OUTPUT'
747 action
['port'] = switch_outport_nr
748 flow
['actions'].append(action
)
750 flow
['match'] = self
._parse
_match
(match
)
751 self
.ryu_REST(prefix
, data
=flow
)
753 def _set_vlan_tag(self
, node
, switch_port
, tag
):
754 node
.vsctl('set', 'port {0} tag={1}'.format(switch_port
,tag
))
755 LOG
.debug("set vlan in switch: {0} in_port: {1} vlan tag: {2}".format(node
.name
, switch_port
, tag
))
757 def _set_flow_entry_dpctl(self
, node
, switch_inport_nr
, switch_outport_nr
, **kwargs
):
759 match
= 'in_port=%s' % switch_inport_nr
761 cookie
= kwargs
.get('cookie')
762 match_input
= kwargs
.get('match')
763 cmd
= kwargs
.get('cmd')
764 path
= kwargs
.get('path')
765 current_hop
= kwargs
.get('current_hop')
766 vlan
= kwargs
.get('vlan')
770 cookie
= 'cookie=%s' % cookie
771 match
= s
.join([cookie
, match
])
773 match
= s
.join([match
, match_input
])
774 if cmd
== 'add-flow':
775 action
= 'action=%s' % switch_outport_nr
777 if path
.index(current_hop
) == 0: # first node
778 action
= ('action=mod_vlan_vid:%s' % vlan
) + (',output=%s' % switch_outport_nr
)
779 match
= '-O OpenFlow13 ' + match
780 elif path
.index(current_hop
) == len(path
) - 1: # last node
781 match
+= ',dl_vlan=%s' % vlan
782 action
= 'action=strip_vlan,output=%s' % switch_outport_nr
784 match
+= ',dl_vlan=%s' % vlan
785 ofcmd
= s
.join([match
, action
])
786 elif cmd
== 'del-flows':
791 node
.dpctl(cmd
, ofcmd
)
792 LOG
.info("{3} in switch: {0} in_port: {1} out_port: {2}".format(node
.name
, switch_inport_nr
,
793 switch_outport_nr
, cmd
))
795 # start Ryu Openflow controller as Remote Controller for the DCNetwork
796 def startRyu(self
, learning_switch
=True):
797 # start Ryu controller with rest-API
798 python_install_path
= site
.getsitepackages()[0]
799 # ryu default learning switch
800 #ryu_path = python_install_path + '/ryu/app/simple_switch_13.py'
801 #custom learning switch that installs a default NORMAL action in the ovs switches
802 dir_path
= os
.path
.dirname(os
.path
.realpath(__file__
))
803 ryu_path
= dir_path
+ '/son_emu_simple_switch_13.py'
804 ryu_path2
= python_install_path
+ '/ryu/app/ofctl_rest.py'
805 # change the default Openflow controller port to 6653 (official IANA-assigned port number), as used by Mininet
806 # Ryu still uses 6633 as default
807 ryu_option
= '--ofp-tcp-listen-port'
809 ryu_cmd
= 'ryu-manager'
810 FNULL
= open("/tmp/ryu.log", 'w')
812 self
.ryu_process
= Popen([ryu_cmd
, ryu_path
, ryu_path2
, ryu_option
, ryu_of_port
], stdout
=FNULL
, stderr
=FNULL
)
813 LOG
.debug('starting ryu-controller with {0}'.format(ryu_path
))
814 LOG
.debug('starting ryu-controller with {0}'.format(ryu_path2
))
816 # no learning switch, but with rest api
817 self
.ryu_process
= Popen([ryu_cmd
, ryu_path2
, ryu_option
, ryu_of_port
], stdout
=FNULL
, stderr
=FNULL
)
818 LOG
.debug('starting ryu-controller with {0}'.format(ryu_path2
))
823 Stop the Ryu controller that might be started by son-emu.
827 if self
.ryu_process
is not None:
828 self
.ryu_process
.terminate()
829 self
.ryu_process
.kill()
830 # ensure its death ;-)
831 Popen(['pkill', '-f', 'ryu-manager'])
833 def ryu_REST(self
, prefix
, dpid
=None, data
=None):
836 url
= self
.ryu_REST_api
+ '/' + str(prefix
) + '/' + str(dpid
)
838 url
= self
.ryu_REST_api
+ '/' + str(prefix
)
840 req
= self
.RyuSession
.post(url
, json
=data
)
842 req
= self
.RyuSession
.get(url
)
845 # do extra logging if status code is not 200 (OK)
846 if req
.status_code
is not requests
.codes
.ok
:
848 'type {0} encoding: {1} text: {2} headers: {3} history: {4}'.format(req
.headers
['content-type'],
849 req
.encoding
, req
.text
,
850 req
.headers
, req
.history
))
851 LOG
.info('url: {0}'.format(str(url
)))
852 if data
: LOG
.info('POST: {0}'.format(str(data
)))
853 LOG
.info('status: {0} reason: {1}'.format(req
.status_code
, req
.reason
))
856 if 'json' in req
.headers
['content-type']:
860 ret
= req
.text
.rstrip()
864 # need to respect that some match fields must be integers
865 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#description-of-match-and-actions
866 def _parse_match(self
, match
):
867 matches
= match
.split(',')
873 m2
= int(match
[1], 0)
877 dict.update({match
[0]:m2
})
880 def find_connected_dc_interface(self
, vnf_src_name
, vnf_src_interface
):
881 for connected_sw
in self
.DCNetwork_graph
.neighbors(vnf_src_name
):
882 link_dict
= self
.DCNetwork_graph
[vnf_src_name
][connected_sw
]
883 for link
in link_dict
:
884 if (link_dict
[link
]['src_port_id'] == vnf_src_interface
or
885 link_dict
[link
]['src_port_name'] == vnf_src_interface
): # Fix: we might also get interface names, e.g, from a son-emu-cli call
886 # found the right link and connected switch
887 src_sw
= connected_sw
888 src_sw_inport_nr
= link_dict
[link
]['dst_port_nr']
889 src_sw_inport_name
= link_dict
[link
]['dst_port_name']
890 return src_sw_inport_name