blob: ec0aee580d45bfc15c81c166311bc492823eff09 [file] [log] [blame]
peustermcbcd4c22015-12-28 11:33:42 +01001"""
peusterm79ef6ae2016-07-08 13:53:57 +02002Copyright (c) 2015 SONATA-NFV and Paderborn University
3ALL RIGHTS RESERVED.
4
5Licensed under the Apache License, Version 2.0 (the "License");
6you may not use this file except in compliance with the License.
7You may obtain a copy of the License at
8
9 http://www.apache.org/licenses/LICENSE-2.0
10
11Unless required by applicable law or agreed to in writing, software
12distributed under the License is distributed on an "AS IS" BASIS,
13WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14See the License for the specific language governing permissions and
15limitations under the License.
16
17Neither the name of the SONATA-NFV [, ANY ADDITIONAL AFFILIATION]
18nor the names of its contributors may be used to endorse or promote
19products derived from this software without specific prior written
20permission.
21
22This work has been performed in the framework of the SONATA project,
23funded by the European Commission under Grant number 671517 through
24the Horizon 2020 and 5G-PPP programmes. The authors would like to
25acknowledge the contributions of their colleagues of the SONATA
26partner consortium (www.sonata-nfv.eu).
peustermcbcd4c22015-12-28 11:33:42 +010027"""
28import logging
29
stevenvanrossem9ebd0942016-02-22 10:13:05 +010030import site
peustermef6629e2016-03-14 17:21:56 +010031import time
stevenvanrossem9ebd0942016-02-22 10:13:05 +010032from subprocess import Popen
stevenvanrossem6b1d9b92016-05-02 13:10:40 +020033import re
stevenvanrossem3fc13932016-08-09 23:39:16 +020034import requests
stevenvanrossemc3a344f2016-11-04 19:34:47 +010035import os
stevenvanrossem9ebd0942016-02-22 10:13:05 +010036
peusterm5877ea22016-05-11 13:44:59 +020037from mininet.net import Containernet
peustermef6629e2016-03-14 17:21:56 +010038from mininet.node import Controller, DefaultController, OVSSwitch, OVSKernelSwitch, Docker, RemoteController
peustermcbcd4c22015-12-28 11:33:42 +010039from mininet.cli import CLI
peustermea8db832016-03-08 10:25:58 +010040from mininet.link import TCLink
peusterm8b04b532016-07-19 16:55:38 +020041from mininet.clean import cleanup
stevenvanrossemc5a536a2016-02-16 14:52:39 +010042import networkx as nx
cgeoffroy9524ad32016-03-03 18:24:15 +010043from emuvim.dcemulator.monitoring import DCNetworkMonitor
cgeoffroy9524ad32016-03-03 18:24:15 +010044from emuvim.dcemulator.node import Datacenter, EmulatorCompute
peusterm42f08be2016-03-10 21:56:34 +010045from emuvim.dcemulator.resourcemodel import ResourceModelRegistrar
peustermcbcd4c22015-12-28 11:33:42 +010046
peustermf9a817d2016-07-18 09:06:04 +020047LOG = logging.getLogger("dcemulator.net")
48LOG.setLevel(logging.DEBUG)
49
peusterm5877ea22016-05-11 13:44:59 +020050class DCNetwork(Containernet):
peusterme4e89d32016-01-07 09:14:54 +010051 """
peusterm5877ea22016-05-11 13:44:59 +020052 Wraps the original Mininet/Containernet class and provides
peusterme4e89d32016-01-07 09:14:54 +010053 methods to add data centers, switches, etc.
54
55 This class is used by topology definition scripts.
56 """
peustermcbcd4c22015-12-28 11:33:42 +010057
peusterm0ec25102016-04-16 02:16:20 +020058 def __init__(self, controller=RemoteController, monitor=False,
stevenvanrossemc3a344f2016-11-04 19:34:47 +010059 enable_learning=False, # learning switch behavior of the default ovs switches icw Ryu controller can be turned off/on, neede for E-LAN functionality
peusterma4d84792016-03-25 12:27:07 +010060 dc_emulation_max_cpu=1.0, # fraction of overall CPU time for emulation
61 dc_emulation_max_mem=512, # emulation max mem in MB
62 **kwargs):
peusterm42f08be2016-03-10 21:56:34 +010063 """
peusterm5877ea22016-05-11 13:44:59 +020064 Create an extended version of a Containernet network
peusterm42f08be2016-03-10 21:56:34 +010065 :param dc_emulation_max_cpu: max. CPU time used by containers in data centers
66 :param kwargs: path through for Mininet parameters
67 :return:
68 """
peusterm8b04b532016-07-19 16:55:38 +020069 # members
peustermcbcd4c22015-12-28 11:33:42 +010070 self.dcs = {}
peusterm8b04b532016-07-19 16:55:38 +020071 self.ryu_process = None
stevenvanrossembecc7c52016-11-07 05:52:01 +010072 #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 = []
77
peusterm42f08be2016-03-10 21:56:34 +010078
peusterm8b04b532016-07-19 16:55:38 +020079 # always cleanup environment before we start the emulator
stevenvanrossem89706802016-07-19 02:54:45 +020080 self.killRyu()
peusterm8b04b532016-07-19 16:55:38 +020081 cleanup()
stevenvanrossem89706802016-07-19 02:54:45 +020082
peusterm293cbc32016-01-13 17:05:28 +010083 # call original Docker.__init__ and setup default controller
peusterm5877ea22016-05-11 13:44:59 +020084 Containernet.__init__(
stevenvanrossem7cd3c252016-05-11 22:55:15 +020085 self, switch=OVSKernelSwitch, controller=controller, **kwargs)
stevenvanrossemc5a536a2016-02-16 14:52:39 +010086
stevenvanrossemc3a344f2016-11-04 19:34:47 +010087 # default switch configuration
88 enable_ryu_learning = False
89 if enable_learning :
90 self.failMode = 'standalone'
91 enable_ryu_learning = True
92 else:
93 self.failMode = 'secure'
94
peustermde14f332016-03-15 16:14:21 +010095 # Ryu management
peustermde14f332016-03-15 16:14:21 +010096 if controller == RemoteController:
97 # start Ryu controller
stevenvanrossemc3a344f2016-11-04 19:34:47 +010098 self.startRyu(learning_switch=enable_ryu_learning)
stevenvanrossem9ebd0942016-02-22 10:13:05 +010099
peustermde14f332016-03-15 16:14:21 +0100100 # add the specified controller
peustermef6629e2016-03-14 17:21:56 +0100101 self.addController('c0', controller=controller)
102
103 # graph of the complete DC network
stevenvanrossemc1149022016-04-11 01:16:44 +0200104 self.DCNetwork_graph = nx.MultiDiGraph()
peustermef6629e2016-03-14 17:21:56 +0100105
stevenvanrossem461941c2016-05-10 11:41:29 +0200106 # initialize pool of vlan tags to setup the SDN paths
107 self.vlans = range(4096)[::-1]
108
stevenvanrossem27b6d952016-05-10 16:37:57 +0200109 # link to Ryu REST_API
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200110 ryu_ip = 'localhost'
stevenvanrossem27b6d952016-05-10 16:37:57 +0200111 ryu_port = '8080'
112 self.ryu_REST_api = 'http://{0}:{1}'.format(ryu_ip, ryu_port)
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200113 self.RyuSession = requests.Session()
stevenvanrossem27b6d952016-05-10 16:37:57 +0200114
peustermef6629e2016-03-14 17:21:56 +0100115 # monitoring agent
stevenvanrossem60670da2016-04-15 15:31:28 +0200116 if monitor:
117 self.monitor_agent = DCNetworkMonitor(self)
118 else:
119 self.monitor_agent = None
peustermef6629e2016-03-14 17:21:56 +0100120
peusterm42f08be2016-03-10 21:56:34 +0100121 # initialize resource model registrar
peusterma4d84792016-03-25 12:27:07 +0100122 self.rm_registrar = ResourceModelRegistrar(
123 dc_emulation_max_cpu, dc_emulation_max_mem)
peustermcbcd4c22015-12-28 11:33:42 +0100124
peusterm60bf8b82016-04-06 14:12:35 +0200125 def addDatacenter(self, label, metadata={}, resource_log_path=None):
peustermcbcd4c22015-12-28 11:33:42 +0100126 """
127 Create and add a logical cloud data center to the network.
128 """
peusterma47db032016-02-04 14:55:29 +0100129 if label in self.dcs:
130 raise Exception("Data center label already exists: %s" % label)
peusterm60bf8b82016-04-06 14:12:35 +0200131 dc = Datacenter(label, metadata=metadata, resource_log_path=resource_log_path)
peustermcbcd4c22015-12-28 11:33:42 +0100132 dc.net = self # set reference to network
peusterma47db032016-02-04 14:55:29 +0100133 self.dcs[label] = dc
peustermcbcd4c22015-12-28 11:33:42 +0100134 dc.create() # finally create the data center in our Mininet instance
peustermf9a817d2016-07-18 09:06:04 +0200135 LOG.info("added data center: %s" % label)
peustermcbcd4c22015-12-28 11:33:42 +0100136 return dc
137
peusterme6092692016-01-11 16:32:58 +0100138 def addLink(self, node1, node2, **params):
peusterm5b844a12016-01-11 15:58:15 +0100139 """
140 Able to handle Datacenter objects as link
141 end points.
142 """
peustermcbcd4c22015-12-28 11:33:42 +0100143 assert node1 is not None
144 assert node2 is not None
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100145
peustermcbcd4c22015-12-28 11:33:42 +0100146 # ensure type of node1
147 if isinstance( node1, basestring ):
148 if node1 in self.dcs:
149 node1 = self.dcs[node1].switch
peustermcbcd4c22015-12-28 11:33:42 +0100150 if isinstance( node1, Datacenter ):
151 node1 = node1.switch
152 # ensure type of node2
153 if isinstance( node2, basestring ):
154 if node2 in self.dcs:
155 node2 = self.dcs[node2].switch
peustermcbcd4c22015-12-28 11:33:42 +0100156 if isinstance( node2, Datacenter ):
157 node2 = node2.switch
peustermc3b977e2016-01-12 10:09:35 +0100158 # try to give containers a default IP
159 if isinstance( node1, Docker ):
peustermea8db832016-03-08 10:25:58 +0100160 if "params1" not in params:
peustermc3b977e2016-01-12 10:09:35 +0100161 params["params1"] = {}
peustermea8db832016-03-08 10:25:58 +0100162 if "ip" not in params["params1"]:
peustermc3b977e2016-01-12 10:09:35 +0100163 params["params1"]["ip"] = self.getNextIp()
164 if isinstance( node2, Docker ):
peustermea8db832016-03-08 10:25:58 +0100165 if "params2" not in params:
peustermc3b977e2016-01-12 10:09:35 +0100166 params["params2"] = {}
peustermea8db832016-03-08 10:25:58 +0100167 if "ip" not in params["params2"]:
peustermc3b977e2016-01-12 10:09:35 +0100168 params["params2"]["ip"] = self.getNextIp()
peustermea8db832016-03-08 10:25:58 +0100169 # 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
peusterm5877ea22016-05-11 13:44:59 +0200171 # see Containernet issue: https://github.com/mpeuster/containernet/issues/3
peustermea8db832016-03-08 10:25:58 +0100172 if "cls" not in params:
173 params["cls"] = TCLink
peustermc3b977e2016-01-12 10:09:35 +0100174
peusterm5877ea22016-05-11 13:44:59 +0200175 link = Containernet.addLink(self, node1, node2, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100176
stevenvanrossemc1149022016-04-11 01:16:44 +0200177 # 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"]
stevenvanrossem5b376412016-05-04 15:34:49 +0200182 node1_port_name = link.intf1.name
stevenvanrossemc1149022016-04-11 01:16:44 +0200183
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"]
stevenvanrossem5b376412016-05-04 15:34:49 +0200188 node2_port_name = link.intf2.name
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200189
190
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100191 # add edge and assigned port number to graph in both directions between node1 and node2
stevenvanrossemc1149022016-04-11 01:16:44 +0200192 # port_id: id given in descriptor (if available, otherwise same as port)
peusterm5877ea22016-05-11 13:44:59 +0200193 # port: portnumber assigned by Containernet
stevenvanrossemc1149022016-04-11 01:16:44 +0200194
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200195 attr_dict = {}
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])
202 if match:
203 attr_number = match.group(1)
204 else:
205 attr_number = None
206 attr_dict[attr] = attr_number
207
208
stevenvanrossem5b376412016-05-04 15:34:49 +0200209 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}
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200213 attr_dict2.update(attr_dict)
214 self.DCNetwork_graph.add_edge(node1.name, node2.name, attr_dict=attr_dict2)
215
stevenvanrossem5b376412016-05-04 15:34:49 +0200216 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}
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200220 attr_dict2.update(attr_dict)
221 self.DCNetwork_graph.add_edge(node2.name, node1.name, attr_dict=attr_dict2)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100222
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100223 LOG.debug("addLink: n1={0} intf1={1} -- n2={2} intf2={3}".format(
224 str(node1),node1_port_name, str(node2), node2_port_name))
225
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100226 return link
peustermcbcd4c22015-12-28 11:33:42 +0100227
peusterma47db032016-02-04 14:55:29 +0100228 def addDocker( self, label, **params ):
peusterm5b844a12016-01-11 15:58:15 +0100229 """
peusterm293cbc32016-01-13 17:05:28 +0100230 Wrapper for addDocker method to use custom container class.
peusterm5b844a12016-01-11 15:58:15 +0100231 """
stevenvanrossem8fbf9782016-02-17 11:40:23 +0100232 self.DCNetwork_graph.add_node(label)
peusterm5877ea22016-05-11 13:44:59 +0200233 return Containernet.addDocker(self, label, cls=EmulatorCompute, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100234
stevenvanrossem8fbf9782016-02-17 11:40:23 +0100235 def removeDocker( self, label, **params ):
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100236 """
237 Wrapper for removeDocker method to update graph.
238 """
stevenvanrossem8fbf9782016-02-17 11:40:23 +0100239 self.DCNetwork_graph.remove_node(label)
peusterm5877ea22016-05-11 13:44:59 +0200240 return Containernet.removeDocker(self, label, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100241
242 def addSwitch( self, name, add_to_graph=True, **params ):
243 """
244 Wrapper for addSwitch method to store switch also in graph.
245 """
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100246
247 # add this switch to the global topology overview
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100248 if add_to_graph:
249 self.DCNetwork_graph.add_node(name)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100250
251 # set the learning switch behavior
252 if 'failMode' in params :
253 failMode = params['failMode']
254 else :
255 failMode = self.failMode
256
257 s = Containernet.addSwitch(self, name, protocols='OpenFlow10,OpenFlow12,OpenFlow13', failMode=failMode, **params)
258
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')
264
265 return s
peustermc3b977e2016-01-12 10:09:35 +0100266
peustermbd44f4a2016-01-13 14:53:30 +0100267 def getAllContainers(self):
268 """
269 Returns a list with all containers within all data centers.
270 """
271 all_containers = []
272 for dc in self.dcs.itervalues():
273 all_containers += dc.listCompute()
274 return all_containers
275
peustermcbcd4c22015-12-28 11:33:42 +0100276 def start(self):
277 # start
278 for dc in self.dcs.itervalues():
279 dc.start()
peusterm5877ea22016-05-11 13:44:59 +0200280 Containernet.start(self)
peustermcbcd4c22015-12-28 11:33:42 +0100281
282 def stop(self):
stevenvanrossem60670da2016-04-15 15:31:28 +0200283
stevenvanrossemc6abf132016-04-14 11:15:58 +0200284 # stop the monitor agent
stevenvanrossem60670da2016-04-15 15:31:28 +0200285 if self.monitor_agent is not None:
286 self.monitor_agent.stop()
peustermcbcd4c22015-12-28 11:33:42 +0100287
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200288 # stop emulator net
peusterm5877ea22016-05-11 13:44:59 +0200289 Containernet.stop(self)
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200290
291 # stop Ryu controller
peusterm8b04b532016-07-19 16:55:38 +0200292 self.killRyu()
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200293
294
peustermcbcd4c22015-12-28 11:33:42 +0100295 def CLI(self):
peusterm293cbc32016-01-13 17:05:28 +0100296 CLI(self)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100297
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100298 def setLAN(self, vnf_list):
299 """
300 setup an E-LAN network by assigning the same VLAN tag to each DC interface of the VNFs in the E-LAN
301
302 :param vnf_list: names of the VNFs in this E-LAN [{name:,interface:},...]
303 :return:
304 """
305 src_sw = None
306 src_sw_inport_nr = 0
307 src_sw_inport_name = None
308
309 # get a vlan tag for this E-LAN
310 vlan = self.vlans.pop()
311
312 for vnf in vnf_list:
313 vnf_src_name = vnf['name']
314 vnf_src_interface = vnf['interface']
315
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']
322
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']
332 break
333
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)
338
stevenvanrossembecc7c52016-11-07 05:52:01 +0100339 def _addMonitorFlow(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None,
340 tag=None, **kwargs):
341
342 src_sw = None
343 src_sw_inport_nr = 0
344 src_sw_inport_name = None
345 dst_sw = None
346 dst_sw_outport_nr = 0
347 dst_sw_outport_name = None
348
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)
351
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']
358
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']
368 break
369
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']
375
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']
386 break
387
388 if not tag >= 0:
389 LOG.exception('tag not valid: {0}'.format(tag))
390
391 # get shortest path
392 try:
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'))
396 except:
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)
404
405 LOG.info("Path between {0} and {1}: {2}".format(vnf_src_name, vnf_dst_name, path))
406
407 current_hop = src_sw
408 switch_inport_nr = src_sw_inport_nr
409
410 cmd = kwargs.get('cmd')
411
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)
415
416 if path.index(current_hop) < len(path)-1:
417 next_hop = path[path.index(current_hop)+1]
418 else:
419 #last switch reached
420 next_hop = vnf_dst_name
421
422 next_node = self.getNodeByName(next_hop)
423
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)
430 else:
431 # take first link between switches by default
432 index_edge_out = 0
433 switch_outport_nr = self.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100434
435
stevenvanrossembecc7c52016-11-07 05:52:01 +0100436 # set of entry via ovs-ofctl
437 if isinstance( current_node, OVSSwitch ):
438 kwargs['vlan'] = tag
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
444
445 monitor_placement = kwargs.get('monitor_placement')
446 # put monitor flow at the dst switch
447 insert_flow = False
448 if monitor_placement == 'tx' and path.index(current_hop) == 0: # first node:
449 insert_flow = True
450 # put monitoring flow at the src switch
451 elif monitor_placement == 'rx' and path.index(current_hop) == len(path) - 1: # last node:
452 insert_flow = True
453 else:
454 LOG.exception('invalid monitor command: {0}'.format(monitor_placement))
455
456
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)
460 break
461 elif insert_flow:
462 ## set flow entry via ovs-ofctl
463 self._set_flow_entry_dpctl(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
464 break
465
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
470
471 return "path {2} between {0} and {1}".format(vnf_src_name, vnf_dst_name, cmd)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100472
473
stevenvanrossem461941c2016-05-10 11:41:29 +0200474 def setChain(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200475 """
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.
479
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
489 """
stevenvanrossembecc7c52016-11-07 05:52:01 +0100490
491 # special procedure for monitoring flows
492 if kwargs.get('monitor'):
493
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)]
498
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)
507 return ret
508 else:
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))
512 pass
513
514
stevenvanrossem461941c2016-05-10 11:41:29 +0200515 cmd = kwargs.get('cmd')
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100516 if cmd == 'add-flow' or cmd == 'del-flows':
stevenvanrossem461941c2016-05-10 11:41:29 +0200517 ret = self._chainAddFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface, **kwargs)
518 if kwargs.get('bidirectional'):
stevenvanrossem81955a52016-05-12 14:34:12 +0200519 ret = ret +'\n' + self._chainAddFlow(vnf_dst_name, vnf_src_name, vnf_dst_interface, vnf_src_interface, **kwargs)
stevenvanrossem9315da42016-04-11 12:10:06 +0200520
stevenvanrossem461941c2016-05-10 11:41:29 +0200521 else:
stevenvanrossem81955a52016-05-12 14:34:12 +0200522 ret = "Command unknown"
523
524 return ret
stevenvanrossem461941c2016-05-10 11:41:29 +0200525
526
527 def _chainAddFlow(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
528
peusterm53d3c142016-07-18 10:10:11 +0200529 src_sw = None
peusterm53d3c142016-07-18 10:10:11 +0200530 src_sw_inport_nr = 0
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100531 src_sw_inport_name = None
532 dst_sw = None
peusterm53d3c142016-07-18 10:10:11 +0200533 dst_sw_outport_nr = 0
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100534 dst_sw_outport_name = None
peusterm53d3c142016-07-18 10:10:11 +0200535
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)
538
stevenvanrossem9315da42016-04-11 12:10:06 +0200539 #check if port is specified (vnf:port)
stevenvanrossemed711fd2016-04-11 16:59:29 +0200540 if vnf_src_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200541 # 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]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200544 vnf_src_interface = link_dict[0]['src_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200545
stevenvanrossem9315da42016-04-11 12:10:06 +0200546 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:
peusterm53d3c142016-07-18 10:10:11 +0200549 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
stevenvanrossem9315da42016-04-11 12:10:06 +0200551 # found the right link and connected switch
stevenvanrossem9315da42016-04-11 12:10:06 +0200552 src_sw = connected_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200553 src_sw_inport_nr = link_dict[link]['dst_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100554 src_sw_inport_name = link_dict[link]['dst_port_name']
stevenvanrossem9315da42016-04-11 12:10:06 +0200555 break
556
stevenvanrossemed711fd2016-04-11 16:59:29 +0200557 if vnf_dst_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200558 # 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]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200561 vnf_dst_interface = link_dict[0]['dst_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200562
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:
peusterm53d3c142016-07-18 10:10:11 +0200567 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
stevenvanrossem9315da42016-04-11 12:10:06 +0200569 # found the right link and connected switch
570 dst_sw = connected_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200571 dst_sw_outport_nr = link_dict[link]['src_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100572 dst_sw_outport_name = link_dict[link]['src_port_name']
stevenvanrossem9315da42016-04-11 12:10:06 +0200573 break
574
575
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100576 # get shortest path
stevenvanrossem9315da42016-04-11 12:10:06 +0200577 try:
stevenvanrossem1f68afb2016-05-02 15:36:04 +0200578 # returns the first found shortest path
579 # if all shortest paths are wanted, use: all_shortest_paths
stevenvanrossem461941c2016-05-10 11:41:29 +0200580 path = nx.shortest_path(self.DCNetwork_graph, src_sw, dst_sw, weight=kwargs.get('weight'))
stevenvanrossem9315da42016-04-11 12:10:06 +0200581 except:
peusterm53d3c142016-07-18 10:10:11 +0200582 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))
peustermf9a817d2016-07-18 09:06:04 +0200584 LOG.debug("Graph nodes: %r" % self.DCNetwork_graph.nodes())
585 LOG.debug("Graph edges: %r" % self.DCNetwork_graph.edges())
peusterm53d3c142016-07-18 10:10:11 +0200586 for e, v in self.DCNetwork_graph.edges():
587 LOG.debug("%r" % self.DCNetwork_graph[e][v])
stevenvanrossem9315da42016-04-11 12:10:06 +0200588 return "No path could be found between {0} and {1}".format(vnf_src_name, vnf_dst_name)
589
peustermf9a817d2016-07-18 09:06:04 +0200590 LOG.info("Path between {0} and {1}: {2}".format(vnf_src_name, vnf_dst_name, path))
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100591
stevenvanrossem9315da42016-04-11 12:10:06 +0200592 current_hop = src_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200593 switch_inport_nr = src_sw_inport_nr
stevenvanrossem9315da42016-04-11 12:10:06 +0200594
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100595 # choose free vlan
596 ## if path contains more than 1 switch
stevenvanrossem27b6d952016-05-10 16:37:57 +0200597 cmd = kwargs.get('cmd')
598 vlan = None
599 if cmd == 'add-flow':
stevenvanrossembecc7c52016-11-07 05:52:01 +0100600 if kwargs.get('tag'):
601 # use pre-defined tag
602 vlan = kwargs.get('tag')
603 else:
604 vlan = self.vlans.pop()
stevenvanrossem461941c2016-05-10 11:41:29 +0200605
stevenvanrossembecc7c52016-11-07 05:52:01 +0100606 # store the used vlan tag to identify this chain
607 if not kwargs.get('monitor'):
608 chain_dict = {}
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)
615
616 #iterate through the path to install the flow-entries
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100617 for i in range(0,len(path)):
stevenvanrossem9315da42016-04-11 12:10:06 +0200618 current_node = self.getNodeByName(current_hop)
stevenvanrossem461941c2016-05-10 11:41:29 +0200619
stevenvanrossem9315da42016-04-11 12:10:06 +0200620 if path.index(current_hop) < len(path)-1:
621 next_hop = path[path.index(current_hop)+1]
622 else:
623 #last switch reached
624 next_hop = vnf_dst_name
625
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100626 next_node = self.getNodeByName(next_hop)
627
628 if next_hop == vnf_dst_name:
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200629 switch_outport_nr = dst_sw_outport_nr
peustermf9a817d2016-07-18 09:06:04 +0200630 LOG.info("end node reached: {0}".format(vnf_dst_name))
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100631 elif not isinstance( next_node, OVSSwitch ):
peustermf9a817d2016-07-18 09:06:04 +0200632 LOG.info("Next node: {0} is not a switch".format(next_hop))
stevenvanrossemeefea6c2016-02-17 12:03:26 +0100633 return "Next node: {0} is not a switch".format(next_hop)
stevenvanrossem9315da42016-04-11 12:10:06 +0200634 else:
stevenvanrossemed711fd2016-04-11 16:59:29 +0200635 # take first link between switches by default
stevenvanrossem9315da42016-04-11 12:10:06 +0200636 index_edge_out = 0
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200637 switch_outport_nr = self.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
stevenvanrossem9315da42016-04-11 12:10:06 +0200638
stevenvanrossem9315da42016-04-11 12:10:06 +0200639
stevenvanrossem461941c2016-05-10 11:41:29 +0200640 # set of entry via ovs-ofctl
stevenvanrossem9315da42016-04-11 12:10:06 +0200641 if isinstance( current_node, OVSSwitch ):
stevenvanrossem461941c2016-05-10 11:41:29 +0200642 kwargs['vlan'] = vlan
643 kwargs['path'] = path
644 kwargs['current_hop'] = current_hop
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100645 kwargs['switch_inport_name'] = src_sw_inport_name
646 kwargs['switch_outport_name'] = dst_sw_outport_name
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200647
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)
651 else:
652 ## set flow entry via ovs-ofctl
653 self._set_flow_entry_dpctl(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
654
stevenvanrossemed711fd2016-04-11 16:59:29 +0200655 # take first link between switches by default
656 if isinstance( next_node, OVSSwitch ):
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200657 switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
stevenvanrossemed711fd2016-04-11 16:59:29 +0200658 current_hop = next_hop
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100659
stevenvanrossem27b6d952016-05-10 16:37:57 +0200660 return "path {2} between {0} and {1}".format(vnf_src_name, vnf_dst_name, cmd)
661
662 def _set_flow_entry_ryu_rest(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
663 match = 'in_port=%s' % switch_inport_nr
664
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')
stevenvanrossem61699eb2016-08-05 15:57:59 +0200671 priority = kwargs.get('priority')
stevenvanrossembecc7c52016-11-07 05:52:01 +0100672 # 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')
676 if not table_id:
677 table_id = 0
stevenvanrossem27b6d952016-05-10 16:37:57 +0200678
679 s = ','
680 if match_input:
681 match = s.join([match, match_input])
682
683 flow = {}
684 flow['dpid'] = int(node.dpid, 16)
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200685
stevenvanrossem27b6d952016-05-10 16:37:57 +0200686 if cookie:
687 flow['cookie'] = int(cookie)
stevenvanrossem61699eb2016-08-05 15:57:59 +0200688 if priority:
689 flow['priority'] = int(priority)
stevenvanrossem27b6d952016-05-10 16:37:57 +0200690
stevenvanrossembecc7c52016-11-07 05:52:01 +0100691 flow['table_id'] = table_id
692
stevenvanrossem27b6d952016-05-10 16:37:57 +0200693 flow['actions'] = []
694
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'
stevenvanrossem27b6d952016-05-10 16:37:57 +0200699 if vlan != None:
700 if path.index(current_hop) == 0: # first node
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100701 # set vlan tag in ovs instance (to isolate E-LANs)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100702 if not skip_vlan_tag:
703 in_port_name = kwargs.get('switch_inport_name')
704 self._set_vlan_tag(node, in_port_name, vlan)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100705 # set vlan push action if more than 1 switch in the path
706 if len(path) > 1:
707 action = {}
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)
711 action = {}
712 action['type'] = 'SET_FIELD'
713 action['field'] = 'vlan_vid'
714 action['value'] = vlan
715 flow['actions'].append(action)
716
717 if path.index(current_hop) == len(path) - 1: # last node
718 # set vlan tag in ovs instance (to isolate E-LANs)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100719 if not skip_vlan_tag:
720 out_port_name = kwargs.get('switch_outport_name')
721 self._set_vlan_tag(node, out_port_name, vlan)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100722 # set vlan pop action if more than 1 switch in the path
723 if len(path) > 1:
724 match += ',dl_vlan=%s' % vlan
725 action = {}
726 action['type'] = 'POP_VLAN'
727 flow['actions'].append(action)
728
729 if 0 < path.index(current_hop) < (len(path) - 1): # middle nodes
stevenvanrossem27b6d952016-05-10 16:37:57 +0200730 match += ',dl_vlan=%s' % vlan
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100731
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200732 # output action must come last
733 action = {}
734 action['type'] = 'OUTPUT'
735 action['port'] = switch_outport_nr
736 flow['actions'].append(action)
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200737
stevenvanrossem27b6d952016-05-10 16:37:57 +0200738 elif cmd == 'del-flows':
stevenvanrossem27b6d952016-05-10 16:37:57 +0200739 prefix = 'stats/flowentry/delete'
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200740
stevenvanrossem27b6d952016-05-10 16:37:57 +0200741 if cookie:
stevenvanrossem1ef77022016-05-12 16:36:10 +0200742 # TODO: add cookie_mask as argument
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200743 flow['cookie_mask'] = int('0xffffffffffffffff', 16) # need full mask to match complete cookie
stevenvanrossem27b6d952016-05-10 16:37:57 +0200744
745 action = {}
746 action['type'] = 'OUTPUT'
747 action['port'] = switch_outport_nr
748 flow['actions'].append(action)
749
750 flow['match'] = self._parse_match(match)
751 self.ryu_REST(prefix, data=flow)
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100752
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100753 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))
756
stevenvanrossem461941c2016-05-10 11:41:29 +0200757 def _set_flow_entry_dpctl(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100758
stevenvanrossem23c48092016-05-06 17:21:12 +0200759 match = 'in_port=%s' % switch_inport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200760
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')
767
stevenvanrossem898a2af2016-05-06 18:28:57 +0200768 s = ','
769 if cookie:
770 cookie = 'cookie=%s' % cookie
771 match = s.join([cookie, match])
stevenvanrossem23c48092016-05-06 17:21:12 +0200772 if match_input:
stevenvanrossem23c48092016-05-06 17:21:12 +0200773 match = s.join([match, match_input])
stevenvanrossem23c48092016-05-06 17:21:12 +0200774 if cmd == 'add-flow':
775 action = 'action=%s' % switch_outport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200776 if vlan != None:
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
783 else: # middle nodes
784 match += ',dl_vlan=%s' % vlan
stevenvanrossem23c48092016-05-06 17:21:12 +0200785 ofcmd = s.join([match, action])
786 elif cmd == 'del-flows':
787 ofcmd = match
788 else:
789 ofcmd = ''
790
791 node.dpctl(cmd, ofcmd)
peustermf9a817d2016-07-18 09:06:04 +0200792 LOG.info("{3} in switch: {0} in_port: {1} out_port: {2}".format(node.name, switch_inport_nr,
stevenvanrossem461941c2016-05-10 11:41:29 +0200793 switch_outport_nr, cmd))
stevenvanrossem23c48092016-05-06 17:21:12 +0200794
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100795 # start Ryu Openflow controller as Remote Controller for the DCNetwork
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200796 def startRyu(self, learning_switch=True):
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100797 # start Ryu controller with rest-API
798 python_install_path = site.getsitepackages()[0]
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100799 # 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'
peustermde14f332016-03-15 16:14:21 +0100804 ryu_path2 = python_install_path + '/ryu/app/ofctl_rest.py'
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100805 # 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'
808 ryu_of_port = '6653'
peustermde14f332016-03-15 16:14:21 +0100809 ryu_cmd = 'ryu-manager'
peustermef6629e2016-03-14 17:21:56 +0100810 FNULL = open("/tmp/ryu.log", 'w')
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200811 if learning_switch:
812 self.ryu_process = Popen([ryu_cmd, ryu_path, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100813 LOG.debug('starting ryu-controller with {0}'.format(ryu_path))
814 LOG.debug('starting ryu-controller with {0}'.format(ryu_path2))
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200815 else:
stevenvanrossem73efd192016-06-29 01:44:07 +0200816 # no learning switch, but with rest api
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200817 self.ryu_process = Popen([ryu_cmd, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100818 LOG.debug('starting ryu-controller with {0}'.format(ryu_path2))
peusterm391773a2016-03-14 17:40:43 +0100819 time.sleep(1)
820
peusterm8b04b532016-07-19 16:55:38 +0200821 def killRyu(self):
822 """
823 Stop the Ryu controller that might be started by son-emu.
824 :return:
825 """
826 # try it nicely
peustermde14f332016-03-15 16:14:21 +0100827 if self.ryu_process is not None:
peusterm391773a2016-03-14 17:40:43 +0100828 self.ryu_process.terminate()
829 self.ryu_process.kill()
peusterm8b04b532016-07-19 16:55:38 +0200830 # ensure its death ;-)
stevenvanrossem89706802016-07-19 02:54:45 +0200831 Popen(['pkill', '-f', 'ryu-manager'])
peusterm391773a2016-03-14 17:40:43 +0100832
stevenvanrossem27b6d952016-05-10 16:37:57 +0200833 def ryu_REST(self, prefix, dpid=None, data=None):
stevenvanrossem27b6d952016-05-10 16:37:57 +0200834
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200835 if dpid:
836 url = self.ryu_REST_api + '/' + str(prefix) + '/' + str(dpid)
837 else:
838 url = self.ryu_REST_api + '/' + str(prefix)
839 if data:
840 req = self.RyuSession.post(url, json=data)
841 else:
842 req = self.RyuSession.get(url)
843
844
845 # do extra logging if status code is not 200 (OK)
846 if req.status_code is not requests.codes.ok:
847 logging.info(
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))
854
855
856 if 'json' in req.headers['content-type']:
857 ret = req.json()
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200858 return ret
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200859
860 ret = req.text.rstrip()
861 return ret
862
stevenvanrossem27b6d952016-05-10 16:37:57 +0200863
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(',')
868 dict = {}
869 for m in matches:
870 match = m.split('=')
871 if len(match) == 2:
872 try:
873 m2 = int(match[1], 0)
874 except:
875 m2 = match[1]
876
877 dict.update({match[0]:m2})
878 return dict
879