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
73 # always cleanup environment before we start the emulator
77 # call original Docker.__init__ and setup default controller
78 Containernet
.__init
__(
79 self
, switch
=OVSKernelSwitch
, controller
=controller
, **kwargs
)
81 # default switch configuration
82 enable_ryu_learning
= False
84 self
.failMode
= 'standalone'
85 enable_ryu_learning
= True
87 self
.failMode
= 'secure'
90 if controller
== RemoteController
:
91 # start Ryu controller
92 self
.startRyu(learning_switch
=enable_ryu_learning
)
94 # add the specified controller
95 self
.addController('c0', controller
=controller
)
97 # graph of the complete DC network
98 self
.DCNetwork_graph
= nx
.MultiDiGraph()
100 # initialize pool of vlan tags to setup the SDN paths
101 self
.vlans
= range(4096)[::-1]
103 # link to Ryu REST_API
106 self
.ryu_REST_api
= 'http://{0}:{1}'.format(ryu_ip
, ryu_port
)
107 self
.RyuSession
= requests
.Session()
111 self
.monitor_agent
= DCNetworkMonitor(self
)
113 self
.monitor_agent
= None
115 # initialize resource model registrar
116 self
.rm_registrar
= ResourceModelRegistrar(
117 dc_emulation_max_cpu
, dc_emulation_max_mem
)
119 def addDatacenter(self
, label
, metadata
={}, resource_log_path
=None):
121 Create and add a logical cloud data center to the network.
123 if label
in self
.dcs
:
124 raise Exception("Data center label already exists: %s" % label
)
125 dc
= Datacenter(label
, metadata
=metadata
, resource_log_path
=resource_log_path
)
126 dc
.net
= self
# set reference to network
128 dc
.create() # finally create the data center in our Mininet instance
129 LOG
.info("added data center: %s" % label
)
132 def addLink(self
, node1
, node2
, **params
):
134 Able to handle Datacenter objects as link
137 assert node1
is not None
138 assert node2
is not None
140 # ensure type of node1
141 if isinstance( node1
, basestring
):
142 if node1
in self
.dcs
:
143 node1
= self
.dcs
[node1
].switch
144 if isinstance( node1
, Datacenter
):
146 # ensure type of node2
147 if isinstance( node2
, basestring
):
148 if node2
in self
.dcs
:
149 node2
= self
.dcs
[node2
].switch
150 if isinstance( node2
, Datacenter
):
152 # try to give containers a default IP
153 if isinstance( node1
, Docker
):
154 if "params1" not in params
:
155 params
["params1"] = {}
156 if "ip" not in params
["params1"]:
157 params
["params1"]["ip"] = self
.getNextIp()
158 if isinstance( node2
, Docker
):
159 if "params2" not in params
:
160 params
["params2"] = {}
161 if "ip" not in params
["params2"]:
162 params
["params2"]["ip"] = self
.getNextIp()
163 # ensure that we allow TCLinks between data centers
164 # TODO this is not optimal, we use cls=Link for containers and TCLink for data centers
165 # see Containernet issue: https://github.com/mpeuster/containernet/issues/3
166 if "cls" not in params
:
167 params
["cls"] = TCLink
169 link
= Containernet
.addLink(self
, node1
, node2
, **params
)
171 # try to give container interfaces a default id
172 node1_port_id
= node1
.ports
[link
.intf1
]
173 if isinstance(node1
, Docker
):
174 if "id" in params
["params1"]:
175 node1_port_id
= params
["params1"]["id"]
176 node1_port_name
= link
.intf1
.name
178 node2_port_id
= node2
.ports
[link
.intf2
]
179 if isinstance(node2
, Docker
):
180 if "id" in params
["params2"]:
181 node2_port_id
= params
["params2"]["id"]
182 node2_port_name
= link
.intf2
.name
185 # add edge and assigned port number to graph in both directions between node1 and node2
186 # port_id: id given in descriptor (if available, otherwise same as port)
187 # port: portnumber assigned by Containernet
190 # possible weight metrics allowed by TClink class:
191 weight_metrics
= ['bw', 'delay', 'jitter', 'loss']
192 edge_attributes
= [p
for p
in params
if p
in weight_metrics
]
193 for attr
in edge_attributes
:
194 # if delay: strip ms (need number as weight in graph)
195 match
= re
.search('([0-9]*\.?[0-9]+)', params
[attr
])
197 attr_number
= match
.group(1)
200 attr_dict
[attr
] = attr_number
203 attr_dict2
= {'src_port_id': node1_port_id
, 'src_port_nr': node1
.ports
[link
.intf1
],
204 'src_port_name': node1_port_name
,
205 'dst_port_id': node2_port_id
, 'dst_port_nr': node2
.ports
[link
.intf2
],
206 'dst_port_name': node2_port_name
}
207 attr_dict2
.update(attr_dict
)
208 self
.DCNetwork_graph
.add_edge(node1
.name
, node2
.name
, attr_dict
=attr_dict2
)
210 attr_dict2
= {'src_port_id': node2_port_id
, 'src_port_nr': node2
.ports
[link
.intf2
],
211 'src_port_name': node2_port_name
,
212 'dst_port_id': node1_port_id
, 'dst_port_nr': node1
.ports
[link
.intf1
],
213 'dst_port_name': node1_port_name
}
214 attr_dict2
.update(attr_dict
)
215 self
.DCNetwork_graph
.add_edge(node2
.name
, node1
.name
, attr_dict
=attr_dict2
)
217 LOG
.debug("addLink: n1={0} intf1={1} -- n2={2} intf2={3}".format(
218 str(node1
),node1_port_name
, str(node2
), node2_port_name
))
222 def addDocker( self
, label
, **params
):
224 Wrapper for addDocker method to use custom container class.
226 self
.DCNetwork_graph
.add_node(label
)
227 return Containernet
.addDocker(self
, label
, cls
=EmulatorCompute
, **params
)
229 def removeDocker( self
, label
, **params
):
231 Wrapper for removeDocker method to update graph.
233 self
.DCNetwork_graph
.remove_node(label
)
234 return Containernet
.removeDocker(self
, label
, **params
)
236 def addSwitch( self
, name
, add_to_graph
=True, **params
):
238 Wrapper for addSwitch method to store switch also in graph.
241 # add this switch to the global topology overview
243 self
.DCNetwork_graph
.add_node(name
)
245 # set the learning switch behavior
246 if 'failMode' in params
:
247 failMode
= params
['failMode']
249 failMode
= self
.failMode
251 s
= Containernet
.addSwitch(self
, name
, protocols
='OpenFlow10,OpenFlow12,OpenFlow13', failMode
=failMode
, **params
)
253 # set flow entry that enables learning switch behavior (needed to enable E-LAN functionality)
254 #LOG.info('failmode {0}'.format(failMode))
255 #if failMode == 'standalone' :
256 # LOG.info('add NORMAL')
257 # s.dpctl('add-flow', 'actions=NORMAL')
261 def getAllContainers(self
):
263 Returns a list with all containers within all data centers.
266 for dc
in self
.dcs
.itervalues():
267 all_containers
+= dc
.listCompute()
268 return all_containers
272 for dc
in self
.dcs
.itervalues():
274 Containernet
.start(self
)
278 # stop the monitor agent
279 if self
.monitor_agent
is not None:
280 self
.monitor_agent
.stop()
283 Containernet
.stop(self
)
285 # stop Ryu controller
292 def setLAN(self
, vnf_list
):
294 setup an E-LAN network by assigning the same VLAN tag to each DC interface of the VNFs in the E-LAN
296 :param vnf_list: names of the VNFs in this E-LAN [{name:,interface:},...]
301 src_sw_inport_name
= None
303 # get a vlan tag for this E-LAN
304 vlan
= self
.vlans
.pop()
307 vnf_src_name
= vnf
['name']
308 vnf_src_interface
= vnf
['interface']
310 # check if port is specified (vnf:port)
311 if vnf_src_interface
is None:
312 # take first interface by default
313 connected_sw
= self
.DCNetwork_graph
.neighbors(vnf_src_name
)[0]
314 link_dict
= self
.DCNetwork_graph
[vnf_src_name
][connected_sw
]
315 vnf_src_interface
= link_dict
[0]['src_port_id']
317 for connected_sw
in self
.DCNetwork_graph
.neighbors(vnf_src_name
):
318 link_dict
= self
.DCNetwork_graph
[vnf_src_name
][connected_sw
]
319 for link
in link_dict
:
320 if (link_dict
[link
]['src_port_id'] == vnf_src_interface
or
321 link_dict
[link
]['src_port_name'] == vnf_src_interface
): # Fix: we might also get interface names, e.g, from a son-emu-cli call
322 # found the right link and connected switch
323 src_sw
= connected_sw
324 src_sw_inport_nr
= link_dict
[link
]['dst_port_nr']
325 src_sw_inport_name
= link_dict
[link
]['dst_port_name']
328 # set the tag on the dc switch interface
329 LOG
.debug('set E-LAN: vnf name: {0} interface: {1} tag: {2}'.format(vnf_src_name
, vnf_src_interface
,vlan
))
330 switch_node
= self
.getNodeByName(src_sw
)
331 self
._set
_vlan
_tag
(switch_node
, src_sw_inport_name
, vlan
)
337 def setChain(self
, vnf_src_name
, vnf_dst_name
, vnf_src_interface
=None, vnf_dst_interface
=None, **kwargs
):
339 Chain 2 vnf interfaces together by installing the flowrules in the switches along their path.
340 Currently the path is found using the default networkx shortest path function.
341 Each chain gets a unique vlan id , so different chains wil not interfere.
343 :param vnf_src_name: vnf name (string)
344 :param vnf_dst_name: vnf name (string)
345 :param vnf_src_interface: source interface name (string)
346 :param vnf_dst_interface: destination interface name (string)
347 :param cmd: 'add-flow' (default) to add a chain, 'del-flows' to remove a chain
348 :param cookie: cookie for the installed flowrules (can be used later as identifier for a set of installed chains)
349 :param match: custom match entry to be added to the flowrules (default: only in_port and vlan tag)
350 :param priority: custom flowrule priority
351 :return: output log string
353 cmd
= kwargs
.get('cmd')
354 if cmd
== 'add-flow' or cmd
== 'del-flows':
355 ret
= self
._chainAddFlow
(vnf_src_name
, vnf_dst_name
, vnf_src_interface
, vnf_dst_interface
, **kwargs
)
356 if kwargs
.get('bidirectional'):
357 ret
= ret
+'\n' + self
._chainAddFlow
(vnf_dst_name
, vnf_src_name
, vnf_dst_interface
, vnf_src_interface
, **kwargs
)
360 ret
= "Command unknown"
365 def _chainAddFlow(self
, vnf_src_name
, vnf_dst_name
, vnf_src_interface
=None, vnf_dst_interface
=None, **kwargs
):
369 src_sw_inport_name
= None
371 dst_sw_outport_nr
= 0
372 dst_sw_outport_name
= None
374 LOG
.debug("call chainAddFlow vnf_src_name=%r, vnf_src_interface=%r, vnf_dst_name=%r, vnf_dst_interface=%r",
375 vnf_src_name
, vnf_src_interface
, vnf_dst_name
, vnf_dst_interface
)
377 #check if port is specified (vnf:port)
378 if vnf_src_interface
is None:
379 # take first interface by default
380 connected_sw
= self
.DCNetwork_graph
.neighbors(vnf_src_name
)[0]
381 link_dict
= self
.DCNetwork_graph
[vnf_src_name
][connected_sw
]
382 vnf_src_interface
= link_dict
[0]['src_port_id']
384 for connected_sw
in self
.DCNetwork_graph
.neighbors(vnf_src_name
):
385 link_dict
= self
.DCNetwork_graph
[vnf_src_name
][connected_sw
]
386 for link
in link_dict
:
387 if (link_dict
[link
]['src_port_id'] == vnf_src_interface
or
388 link_dict
[link
]['src_port_name'] == vnf_src_interface
): # Fix: we might also get interface names, e.g, from a son-emu-cli call
389 # found the right link and connected switch
390 src_sw
= connected_sw
391 src_sw_inport_nr
= link_dict
[link
]['dst_port_nr']
392 src_sw_inport_name
= link_dict
[link
]['dst_port_name']
395 if vnf_dst_interface
is None:
396 # take first interface by default
397 connected_sw
= self
.DCNetwork_graph
.neighbors(vnf_dst_name
)[0]
398 link_dict
= self
.DCNetwork_graph
[connected_sw
][vnf_dst_name
]
399 vnf_dst_interface
= link_dict
[0]['dst_port_id']
401 vnf_dst_name
= vnf_dst_name
.split(':')[0]
402 for connected_sw
in self
.DCNetwork_graph
.neighbors(vnf_dst_name
):
403 link_dict
= self
.DCNetwork_graph
[connected_sw
][vnf_dst_name
]
404 for link
in link_dict
:
405 if link_dict
[link
]['dst_port_id'] == vnf_dst_interface
or \
406 link_dict
[link
]['dst_port_name'] == vnf_dst_interface
: # Fix: we might also get interface names, e.g, from a son-emu-cli call
407 # found the right link and connected switch
408 dst_sw
= connected_sw
409 dst_sw_outport_nr
= link_dict
[link
]['src_port_nr']
410 dst_sw_outport_name
= link_dict
[link
]['src_port_name']
416 # returns the first found shortest path
417 # if all shortest paths are wanted, use: all_shortest_paths
418 path
= nx
.shortest_path(self
.DCNetwork_graph
, src_sw
, dst_sw
, weight
=kwargs
.get('weight'))
420 LOG
.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
421 vnf_src_name
, vnf_dst_name
, src_sw
, dst_sw
))
422 LOG
.debug("Graph nodes: %r" % self
.DCNetwork_graph
.nodes())
423 LOG
.debug("Graph edges: %r" % self
.DCNetwork_graph
.edges())
424 for e
, v
in self
.DCNetwork_graph
.edges():
425 LOG
.debug("%r" % self
.DCNetwork_graph
[e
][v
])
426 return "No path could be found between {0} and {1}".format(vnf_src_name
, vnf_dst_name
)
428 LOG
.info("Path between {0} and {1}: {2}".format(vnf_src_name
, vnf_dst_name
, path
))
431 switch_inport_nr
= src_sw_inport_nr
434 ## if path contains more than 1 switch
435 cmd
= kwargs
.get('cmd')
437 if cmd
== 'add-flow':
438 vlan
= self
.vlans
.pop()
440 ## vlan = self.vlans.pop()
442 for i
in range(0,len(path
)):
443 current_node
= self
.getNodeByName(current_hop
)
445 if path
.index(current_hop
) < len(path
)-1:
446 next_hop
= path
[path
.index(current_hop
)+1]
449 next_hop
= vnf_dst_name
451 next_node
= self
.getNodeByName(next_hop
)
453 if next_hop
== vnf_dst_name
:
454 switch_outport_nr
= dst_sw_outport_nr
455 LOG
.info("end node reached: {0}".format(vnf_dst_name
))
456 elif not isinstance( next_node
, OVSSwitch
):
457 LOG
.info("Next node: {0} is not a switch".format(next_hop
))
458 return "Next node: {0} is not a switch".format(next_hop
)
460 # take first link between switches by default
462 switch_outport_nr
= self
.DCNetwork_graph
[current_hop
][next_hop
][index_edge_out
]['src_port_nr']
465 # set of entry via ovs-ofctl
466 if isinstance( current_node
, OVSSwitch
):
467 kwargs
['vlan'] = vlan
468 kwargs
['path'] = path
469 kwargs
['current_hop'] = current_hop
470 kwargs
['switch_inport_name'] = src_sw_inport_name
471 kwargs
['switch_outport_name'] = dst_sw_outport_name
473 if self
.controller
== RemoteController
:
474 ## set flow entry via ryu rest api
475 self
._set
_flow
_entry
_ryu
_rest
(current_node
, switch_inport_nr
, switch_outport_nr
, **kwargs
)
477 ## set flow entry via ovs-ofctl
478 self
._set
_flow
_entry
_dpctl
(current_node
, switch_inport_nr
, switch_outport_nr
, **kwargs
)
482 # take first link between switches by default
483 if isinstance( next_node
, OVSSwitch
):
484 switch_inport_nr
= self
.DCNetwork_graph
[current_hop
][next_hop
][0]['dst_port_nr']
485 current_hop
= next_hop
487 return "path {2} between {0} and {1}".format(vnf_src_name
, vnf_dst_name
, cmd
)
489 def _set_flow_entry_ryu_rest(self
, node
, switch_inport_nr
, switch_outport_nr
, **kwargs
):
490 match
= 'in_port=%s' % switch_inport_nr
492 cookie
= kwargs
.get('cookie')
493 match_input
= kwargs
.get('match')
494 cmd
= kwargs
.get('cmd')
495 path
= kwargs
.get('path')
496 current_hop
= kwargs
.get('current_hop')
497 vlan
= kwargs
.get('vlan')
498 priority
= kwargs
.get('priority')
502 match
= s
.join([match
, match_input
])
505 flow
['dpid'] = int(node
.dpid
, 16)
508 flow
['cookie'] = int(cookie
)
510 flow
['priority'] = int(priority
)
514 # possible Ryu actions, match fields:
515 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#add-a-flow-entry
516 if cmd
== 'add-flow':
517 prefix
= 'stats/flowentry/add'
519 if path
.index(current_hop
) == 0: # first node
520 # set vlan tag in ovs instance (to isolate E-LANs)
521 in_port_name
= kwargs
.get('switch_inport_name')
522 self
._set
_vlan
_tag
(node
, in_port_name
, vlan
)
523 # set vlan push action if more than 1 switch in the path
526 action
['type'] = 'PUSH_VLAN' # Push a new VLAN tag if a input frame is non-VLAN-tagged
527 action
['ethertype'] = 33024 # Ethertype 0x8100(=33024): IEEE 802.1Q VLAN-tagged frame
528 flow
['actions'].append(action
)
530 action
['type'] = 'SET_FIELD'
531 action
['field'] = 'vlan_vid'
532 action
['value'] = vlan
533 flow
['actions'].append(action
)
535 if path
.index(current_hop
) == len(path
) - 1: # last node
536 # set vlan tag in ovs instance (to isolate E-LANs)
537 out_port_name
= kwargs
.get('switch_outport_name')
538 self
._set
_vlan
_tag
(node
, out_port_name
, vlan
)
539 # set vlan pop action if more than 1 switch in the path
541 match
+= ',dl_vlan=%s' % vlan
543 action
['type'] = 'POP_VLAN'
544 flow
['actions'].append(action
)
546 if 0 < path
.index(current_hop
) < (len(path
) - 1): # middle nodes
547 match
+= ',dl_vlan=%s' % vlan
549 # output action must come last
551 action
['type'] = 'OUTPUT'
552 action
['port'] = switch_outport_nr
553 flow
['actions'].append(action
)
555 elif cmd
== 'del-flows':
556 prefix
= 'stats/flowentry/delete'
559 # TODO: add cookie_mask as argument
560 flow
['cookie_mask'] = int('0xffffffffffffffff', 16) # need full mask to match complete cookie
563 action
['type'] = 'OUTPUT'
564 action
['port'] = switch_outport_nr
565 flow
['actions'].append(action
)
567 flow
['match'] = self
._parse
_match
(match
)
568 self
.ryu_REST(prefix
, data
=flow
)
570 def _set_vlan_tag(self
, node
, switch_port
, tag
):
571 node
.vsctl('set', 'port {0} tag={1}'.format(switch_port
,tag
))
572 LOG
.debug("set vlan in switch: {0} in_port: {1} vlan tag: {2}".format(node
.name
, switch_port
, tag
))
574 def _set_flow_entry_dpctl(self
, node
, switch_inport_nr
, switch_outport_nr
, **kwargs
):
576 match
= 'in_port=%s' % switch_inport_nr
578 cookie
= kwargs
.get('cookie')
579 match_input
= kwargs
.get('match')
580 cmd
= kwargs
.get('cmd')
581 path
= kwargs
.get('path')
582 current_hop
= kwargs
.get('current_hop')
583 vlan
= kwargs
.get('vlan')
587 cookie
= 'cookie=%s' % cookie
588 match
= s
.join([cookie
, match
])
590 match
= s
.join([match
, match_input
])
591 if cmd
== 'add-flow':
592 action
= 'action=%s' % switch_outport_nr
594 if path
.index(current_hop
) == 0: # first node
595 action
= ('action=mod_vlan_vid:%s' % vlan
) + (',output=%s' % switch_outport_nr
)
596 match
= '-O OpenFlow13 ' + match
597 elif path
.index(current_hop
) == len(path
) - 1: # last node
598 match
+= ',dl_vlan=%s' % vlan
599 action
= 'action=strip_vlan,output=%s' % switch_outport_nr
601 match
+= ',dl_vlan=%s' % vlan
602 ofcmd
= s
.join([match
, action
])
603 elif cmd
== 'del-flows':
608 node
.dpctl(cmd
, ofcmd
)
609 LOG
.info("{3} in switch: {0} in_port: {1} out_port: {2}".format(node
.name
, switch_inport_nr
,
610 switch_outport_nr
, cmd
))
612 # start Ryu Openflow controller as Remote Controller for the DCNetwork
613 def startRyu(self
, learning_switch
=True):
614 # start Ryu controller with rest-API
615 python_install_path
= site
.getsitepackages()[0]
616 # ryu default learning switch
617 #ryu_path = python_install_path + '/ryu/app/simple_switch_13.py'
618 #custom learning switch that installs a default NORMAL action in the ovs switches
619 dir_path
= os
.path
.dirname(os
.path
.realpath(__file__
))
620 ryu_path
= dir_path
+ '/son_emu_simple_switch_13.py'
621 ryu_path2
= python_install_path
+ '/ryu/app/ofctl_rest.py'
622 # change the default Openflow controller port to 6653 (official IANA-assigned port number), as used by Mininet
623 # Ryu still uses 6633 as default
624 ryu_option
= '--ofp-tcp-listen-port'
626 ryu_cmd
= 'ryu-manager'
627 FNULL
= open("/tmp/ryu.log", 'w')
629 self
.ryu_process
= Popen([ryu_cmd
, ryu_path
, ryu_path2
, ryu_option
, ryu_of_port
], stdout
=FNULL
, stderr
=FNULL
)
630 LOG
.debug('starting ryu-controller with {0}'.format(ryu_path
))
631 LOG
.debug('starting ryu-controller with {0}'.format(ryu_path2
))
633 # no learning switch, but with rest api
634 self
.ryu_process
= Popen([ryu_cmd
, ryu_path2
, ryu_option
, ryu_of_port
], stdout
=FNULL
, stderr
=FNULL
)
635 LOG
.debug('starting ryu-controller with {0}'.format(ryu_path2
))
640 Stop the Ryu controller that might be started by son-emu.
644 if self
.ryu_process
is not None:
645 self
.ryu_process
.terminate()
646 self
.ryu_process
.kill()
647 # ensure its death ;-)
648 Popen(['pkill', '-f', 'ryu-manager'])
650 def ryu_REST(self
, prefix
, dpid
=None, data
=None):
653 url
= self
.ryu_REST_api
+ '/' + str(prefix
) + '/' + str(dpid
)
655 url
= self
.ryu_REST_api
+ '/' + str(prefix
)
657 req
= self
.RyuSession
.post(url
, json
=data
)
659 req
= self
.RyuSession
.get(url
)
662 # do extra logging if status code is not 200 (OK)
663 if req
.status_code
is not requests
.codes
.ok
:
665 'type {0} encoding: {1} text: {2} headers: {3} history: {4}'.format(req
.headers
['content-type'],
666 req
.encoding
, req
.text
,
667 req
.headers
, req
.history
))
668 LOG
.info('url: {0}'.format(str(url
)))
669 if data
: LOG
.info('POST: {0}'.format(str(data
)))
670 LOG
.info('status: {0} reason: {1}'.format(req
.status_code
, req
.reason
))
673 if 'json' in req
.headers
['content-type']:
677 ret
= req
.text
.rstrip()
681 # need to respect that some match fields must be integers
682 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#description-of-match-and-actions
683 def _parse_match(self
, match
):
684 matches
= match
.split(',')
690 m2
= int(match
[1], 0)
694 dict.update({match
[0]:m2
})