blob: 3b4dd5442fa4ae444e058390fe8b25dbfb2c284d [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
stevenvanrossemb3f34172016-11-16 23:30:57 +010050# default CPU period used for cpu percentage-based cfs values (microseconds)
51CPU_PERIOD = 1000000
52
peusterm5877ea22016-05-11 13:44:59 +020053class DCNetwork(Containernet):
peusterme4e89d32016-01-07 09:14:54 +010054 """
peusterm5877ea22016-05-11 13:44:59 +020055 Wraps the original Mininet/Containernet class and provides
peusterme4e89d32016-01-07 09:14:54 +010056 methods to add data centers, switches, etc.
57
58 This class is used by topology definition scripts.
59 """
peustermcbcd4c22015-12-28 11:33:42 +010060
peusterm0ec25102016-04-16 02:16:20 +020061 def __init__(self, controller=RemoteController, monitor=False,
stevenvanrossemb3f34172016-11-16 23:30:57 +010062 enable_learning=False, # learning switch behavior of the default ovs switches icw Ryu controller can be turned off/on, needed for E-LAN functionality
peusterma4d84792016-03-25 12:27:07 +010063 dc_emulation_max_cpu=1.0, # fraction of overall CPU time for emulation
64 dc_emulation_max_mem=512, # emulation max mem in MB
65 **kwargs):
peusterm42f08be2016-03-10 21:56:34 +010066 """
peusterm5877ea22016-05-11 13:44:59 +020067 Create an extended version of a Containernet network
peusterm42f08be2016-03-10 21:56:34 +010068 :param dc_emulation_max_cpu: max. CPU time used by containers in data centers
69 :param kwargs: path through for Mininet parameters
70 :return:
71 """
peusterm8b04b532016-07-19 16:55:38 +020072 # members
peustermcbcd4c22015-12-28 11:33:42 +010073 self.dcs = {}
peusterm8b04b532016-07-19 16:55:38 +020074 self.ryu_process = None
stevenvanrossembecc7c52016-11-07 05:52:01 +010075 #list of deployed nsds.E_Lines and E_LANs (uploaded from the dummy gatekeeper)
76 self.deployed_nsds = []
77 self.deployed_elines = []
78 self.deployed_elans = []
79 self.installed_chains = []
80
peusterm42f08be2016-03-10 21:56:34 +010081
peusterm8b04b532016-07-19 16:55:38 +020082 # always cleanup environment before we start the emulator
stevenvanrossem89706802016-07-19 02:54:45 +020083 self.killRyu()
peusterm8b04b532016-07-19 16:55:38 +020084 cleanup()
stevenvanrossem89706802016-07-19 02:54:45 +020085
peusterm293cbc32016-01-13 17:05:28 +010086 # call original Docker.__init__ and setup default controller
peusterm5877ea22016-05-11 13:44:59 +020087 Containernet.__init__(
stevenvanrossem7cd3c252016-05-11 22:55:15 +020088 self, switch=OVSKernelSwitch, controller=controller, **kwargs)
stevenvanrossemc5a536a2016-02-16 14:52:39 +010089
stevenvanrossemc3a344f2016-11-04 19:34:47 +010090 # default switch configuration
91 enable_ryu_learning = False
92 if enable_learning :
93 self.failMode = 'standalone'
94 enable_ryu_learning = True
95 else:
96 self.failMode = 'secure'
97
peustermde14f332016-03-15 16:14:21 +010098 # Ryu management
peustermde14f332016-03-15 16:14:21 +010099 if controller == RemoteController:
100 # start Ryu controller
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100101 self.startRyu(learning_switch=enable_ryu_learning)
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100102
peustermde14f332016-03-15 16:14:21 +0100103 # add the specified controller
peustermef6629e2016-03-14 17:21:56 +0100104 self.addController('c0', controller=controller)
105
106 # graph of the complete DC network
stevenvanrossemc1149022016-04-11 01:16:44 +0200107 self.DCNetwork_graph = nx.MultiDiGraph()
peustermef6629e2016-03-14 17:21:56 +0100108
stevenvanrossem461941c2016-05-10 11:41:29 +0200109 # initialize pool of vlan tags to setup the SDN paths
110 self.vlans = range(4096)[::-1]
111
stevenvanrossem27b6d952016-05-10 16:37:57 +0200112 # link to Ryu REST_API
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200113 ryu_ip = 'localhost'
stevenvanrossem27b6d952016-05-10 16:37:57 +0200114 ryu_port = '8080'
115 self.ryu_REST_api = 'http://{0}:{1}'.format(ryu_ip, ryu_port)
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200116 self.RyuSession = requests.Session()
stevenvanrossem27b6d952016-05-10 16:37:57 +0200117
peustermef6629e2016-03-14 17:21:56 +0100118 # monitoring agent
stevenvanrossem60670da2016-04-15 15:31:28 +0200119 if monitor:
120 self.monitor_agent = DCNetworkMonitor(self)
121 else:
122 self.monitor_agent = None
peustermef6629e2016-03-14 17:21:56 +0100123
peusterm42f08be2016-03-10 21:56:34 +0100124 # initialize resource model registrar
peusterma4d84792016-03-25 12:27:07 +0100125 self.rm_registrar = ResourceModelRegistrar(
126 dc_emulation_max_cpu, dc_emulation_max_mem)
stevenvanrossemb3f34172016-11-16 23:30:57 +0100127 self.cpu_period = CPU_PERIOD
peustermcbcd4c22015-12-28 11:33:42 +0100128
peusterm60bf8b82016-04-06 14:12:35 +0200129 def addDatacenter(self, label, metadata={}, resource_log_path=None):
peustermcbcd4c22015-12-28 11:33:42 +0100130 """
131 Create and add a logical cloud data center to the network.
132 """
peusterma47db032016-02-04 14:55:29 +0100133 if label in self.dcs:
134 raise Exception("Data center label already exists: %s" % label)
peusterm60bf8b82016-04-06 14:12:35 +0200135 dc = Datacenter(label, metadata=metadata, resource_log_path=resource_log_path)
peustermcbcd4c22015-12-28 11:33:42 +0100136 dc.net = self # set reference to network
peusterma47db032016-02-04 14:55:29 +0100137 self.dcs[label] = dc
peustermcbcd4c22015-12-28 11:33:42 +0100138 dc.create() # finally create the data center in our Mininet instance
peustermf9a817d2016-07-18 09:06:04 +0200139 LOG.info("added data center: %s" % label)
peustermcbcd4c22015-12-28 11:33:42 +0100140 return dc
141
peusterme6092692016-01-11 16:32:58 +0100142 def addLink(self, node1, node2, **params):
peusterm5b844a12016-01-11 15:58:15 +0100143 """
144 Able to handle Datacenter objects as link
145 end points.
146 """
peustermcbcd4c22015-12-28 11:33:42 +0100147 assert node1 is not None
148 assert node2 is not None
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100149
peustermcbcd4c22015-12-28 11:33:42 +0100150 # ensure type of node1
151 if isinstance( node1, basestring ):
152 if node1 in self.dcs:
153 node1 = self.dcs[node1].switch
peustermcbcd4c22015-12-28 11:33:42 +0100154 if isinstance( node1, Datacenter ):
155 node1 = node1.switch
156 # ensure type of node2
157 if isinstance( node2, basestring ):
158 if node2 in self.dcs:
159 node2 = self.dcs[node2].switch
peustermcbcd4c22015-12-28 11:33:42 +0100160 if isinstance( node2, Datacenter ):
161 node2 = node2.switch
peustermc3b977e2016-01-12 10:09:35 +0100162 # try to give containers a default IP
163 if isinstance( node1, Docker ):
peustermea8db832016-03-08 10:25:58 +0100164 if "params1" not in params:
peustermc3b977e2016-01-12 10:09:35 +0100165 params["params1"] = {}
peustermea8db832016-03-08 10:25:58 +0100166 if "ip" not in params["params1"]:
peustermc3b977e2016-01-12 10:09:35 +0100167 params["params1"]["ip"] = self.getNextIp()
168 if isinstance( node2, Docker ):
peustermea8db832016-03-08 10:25:58 +0100169 if "params2" not in params:
peustermc3b977e2016-01-12 10:09:35 +0100170 params["params2"] = {}
peustermea8db832016-03-08 10:25:58 +0100171 if "ip" not in params["params2"]:
peustermc3b977e2016-01-12 10:09:35 +0100172 params["params2"]["ip"] = self.getNextIp()
peustermea8db832016-03-08 10:25:58 +0100173 # ensure that we allow TCLinks between data centers
174 # TODO this is not optimal, we use cls=Link for containers and TCLink for data centers
peusterm5877ea22016-05-11 13:44:59 +0200175 # see Containernet issue: https://github.com/mpeuster/containernet/issues/3
peustermea8db832016-03-08 10:25:58 +0100176 if "cls" not in params:
177 params["cls"] = TCLink
peustermc3b977e2016-01-12 10:09:35 +0100178
peusterm5877ea22016-05-11 13:44:59 +0200179 link = Containernet.addLink(self, node1, node2, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100180
stevenvanrossemc1149022016-04-11 01:16:44 +0200181 # try to give container interfaces a default id
182 node1_port_id = node1.ports[link.intf1]
183 if isinstance(node1, Docker):
184 if "id" in params["params1"]:
185 node1_port_id = params["params1"]["id"]
stevenvanrossem5b376412016-05-04 15:34:49 +0200186 node1_port_name = link.intf1.name
stevenvanrossemc1149022016-04-11 01:16:44 +0200187
188 node2_port_id = node2.ports[link.intf2]
189 if isinstance(node2, Docker):
190 if "id" in params["params2"]:
191 node2_port_id = params["params2"]["id"]
stevenvanrossem5b376412016-05-04 15:34:49 +0200192 node2_port_name = link.intf2.name
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200193
194
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100195 # add edge and assigned port number to graph in both directions between node1 and node2
stevenvanrossemc1149022016-04-11 01:16:44 +0200196 # port_id: id given in descriptor (if available, otherwise same as port)
peusterm5877ea22016-05-11 13:44:59 +0200197 # port: portnumber assigned by Containernet
stevenvanrossemc1149022016-04-11 01:16:44 +0200198
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200199 attr_dict = {}
200 # possible weight metrics allowed by TClink class:
201 weight_metrics = ['bw', 'delay', 'jitter', 'loss']
202 edge_attributes = [p for p in params if p in weight_metrics]
203 for attr in edge_attributes:
204 # if delay: strip ms (need number as weight in graph)
205 match = re.search('([0-9]*\.?[0-9]+)', params[attr])
206 if match:
207 attr_number = match.group(1)
208 else:
209 attr_number = None
210 attr_dict[attr] = attr_number
211
212
stevenvanrossem5b376412016-05-04 15:34:49 +0200213 attr_dict2 = {'src_port_id': node1_port_id, 'src_port_nr': node1.ports[link.intf1],
214 'src_port_name': node1_port_name,
215 'dst_port_id': node2_port_id, 'dst_port_nr': node2.ports[link.intf2],
216 'dst_port_name': node2_port_name}
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200217 attr_dict2.update(attr_dict)
218 self.DCNetwork_graph.add_edge(node1.name, node2.name, attr_dict=attr_dict2)
219
stevenvanrossem5b376412016-05-04 15:34:49 +0200220 attr_dict2 = {'src_port_id': node2_port_id, 'src_port_nr': node2.ports[link.intf2],
221 'src_port_name': node2_port_name,
222 'dst_port_id': node1_port_id, 'dst_port_nr': node1.ports[link.intf1],
223 'dst_port_name': node1_port_name}
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200224 attr_dict2.update(attr_dict)
225 self.DCNetwork_graph.add_edge(node2.name, node1.name, attr_dict=attr_dict2)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100226
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100227 LOG.debug("addLink: n1={0} intf1={1} -- n2={2} intf2={3}".format(
228 str(node1),node1_port_name, str(node2), node2_port_name))
229
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100230 return link
peustermcbcd4c22015-12-28 11:33:42 +0100231
peusterma47db032016-02-04 14:55:29 +0100232 def addDocker( self, label, **params ):
peusterm5b844a12016-01-11 15:58:15 +0100233 """
peusterm293cbc32016-01-13 17:05:28 +0100234 Wrapper for addDocker method to use custom container class.
peusterm5b844a12016-01-11 15:58:15 +0100235 """
stevenvanrossem8fbf9782016-02-17 11:40:23 +0100236 self.DCNetwork_graph.add_node(label)
peusterm5877ea22016-05-11 13:44:59 +0200237 return Containernet.addDocker(self, label, cls=EmulatorCompute, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100238
stevenvanrossem8fbf9782016-02-17 11:40:23 +0100239 def removeDocker( self, label, **params ):
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100240 """
241 Wrapper for removeDocker method to update graph.
242 """
stevenvanrossem8fbf9782016-02-17 11:40:23 +0100243 self.DCNetwork_graph.remove_node(label)
peusterm5877ea22016-05-11 13:44:59 +0200244 return Containernet.removeDocker(self, label, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100245
246 def addSwitch( self, name, add_to_graph=True, **params ):
247 """
248 Wrapper for addSwitch method to store switch also in graph.
249 """
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100250
251 # add this switch to the global topology overview
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100252 if add_to_graph:
253 self.DCNetwork_graph.add_node(name)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100254
255 # set the learning switch behavior
256 if 'failMode' in params :
257 failMode = params['failMode']
258 else :
259 failMode = self.failMode
260
261 s = Containernet.addSwitch(self, name, protocols='OpenFlow10,OpenFlow12,OpenFlow13', failMode=failMode, **params)
262
263 # set flow entry that enables learning switch behavior (needed to enable E-LAN functionality)
264 #LOG.info('failmode {0}'.format(failMode))
265 #if failMode == 'standalone' :
266 # LOG.info('add NORMAL')
267 # s.dpctl('add-flow', 'actions=NORMAL')
268
269 return s
peustermc3b977e2016-01-12 10:09:35 +0100270
peustermbd44f4a2016-01-13 14:53:30 +0100271 def getAllContainers(self):
272 """
273 Returns a list with all containers within all data centers.
274 """
275 all_containers = []
276 for dc in self.dcs.itervalues():
277 all_containers += dc.listCompute()
278 return all_containers
279
peustermcbcd4c22015-12-28 11:33:42 +0100280 def start(self):
281 # start
282 for dc in self.dcs.itervalues():
283 dc.start()
peusterm5877ea22016-05-11 13:44:59 +0200284 Containernet.start(self)
peustermcbcd4c22015-12-28 11:33:42 +0100285
286 def stop(self):
stevenvanrossem60670da2016-04-15 15:31:28 +0200287
stevenvanrossemc6abf132016-04-14 11:15:58 +0200288 # stop the monitor agent
stevenvanrossem60670da2016-04-15 15:31:28 +0200289 if self.monitor_agent is not None:
290 self.monitor_agent.stop()
peustermcbcd4c22015-12-28 11:33:42 +0100291
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200292 # stop emulator net
peusterm5877ea22016-05-11 13:44:59 +0200293 Containernet.stop(self)
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200294
295 # stop Ryu controller
peusterm8b04b532016-07-19 16:55:38 +0200296 self.killRyu()
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200297
298
peustermcbcd4c22015-12-28 11:33:42 +0100299 def CLI(self):
peusterm293cbc32016-01-13 17:05:28 +0100300 CLI(self)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100301
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100302 def setLAN(self, vnf_list):
303 """
304 setup an E-LAN network by assigning the same VLAN tag to each DC interface of the VNFs in the E-LAN
305
306 :param vnf_list: names of the VNFs in this E-LAN [{name:,interface:},...]
307 :return:
308 """
309 src_sw = None
310 src_sw_inport_nr = 0
311 src_sw_inport_name = None
312
313 # get a vlan tag for this E-LAN
314 vlan = self.vlans.pop()
315
316 for vnf in vnf_list:
317 vnf_src_name = vnf['name']
318 vnf_src_interface = vnf['interface']
319
320 # check if port is specified (vnf:port)
321 if vnf_src_interface is None:
322 # take first interface by default
323 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
324 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
325 vnf_src_interface = link_dict[0]['src_port_id']
326
327 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
328 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
329 for link in link_dict:
330 if (link_dict[link]['src_port_id'] == vnf_src_interface or
331 link_dict[link]['src_port_name'] == vnf_src_interface): # Fix: we might also get interface names, e.g, from a son-emu-cli call
332 # found the right link and connected switch
333 src_sw = connected_sw
334 src_sw_inport_nr = link_dict[link]['dst_port_nr']
335 src_sw_inport_name = link_dict[link]['dst_port_name']
336 break
337
338 # set the tag on the dc switch interface
339 LOG.debug('set E-LAN: vnf name: {0} interface: {1} tag: {2}'.format(vnf_src_name, vnf_src_interface,vlan))
340 switch_node = self.getNodeByName(src_sw)
341 self._set_vlan_tag(switch_node, src_sw_inport_name, vlan)
342
stevenvanrossembecc7c52016-11-07 05:52:01 +0100343 def _addMonitorFlow(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None,
344 tag=None, **kwargs):
345
346 src_sw = None
347 src_sw_inport_nr = 0
348 src_sw_inport_name = None
349 dst_sw = None
350 dst_sw_outport_nr = 0
351 dst_sw_outport_name = None
352
353 LOG.debug("call AddMonitorFlow vnf_src_name=%r, vnf_src_interface=%r, vnf_dst_name=%r, vnf_dst_interface=%r",
354 vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface)
355
356 #check if port is specified (vnf:port)
357 if vnf_src_interface is None:
358 # take first interface by default
359 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
360 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
361 vnf_src_interface = link_dict[0]['src_port_id']
362
363 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
364 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
365 for link in link_dict:
366 if (link_dict[link]['src_port_id'] == vnf_src_interface or
367 link_dict[link]['src_port_name'] == vnf_src_interface): # Fix: we might also get interface names, e.g, from a son-emu-cli call
368 # found the right link and connected switch
369 src_sw = connected_sw
370 src_sw_inport_nr = link_dict[link]['dst_port_nr']
371 src_sw_inport_name = link_dict[link]['dst_port_name']
372 break
373
374 if vnf_dst_interface is None:
375 # take first interface by default
376 connected_sw = self.DCNetwork_graph.neighbors(vnf_dst_name)[0]
377 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
378 vnf_dst_interface = link_dict[0]['dst_port_id']
379
380 vnf_dst_name = vnf_dst_name.split(':')[0]
381 for connected_sw in self.DCNetwork_graph.neighbors(vnf_dst_name):
382 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
383 for link in link_dict:
384 if link_dict[link]['dst_port_id'] == vnf_dst_interface or \
385 link_dict[link]['dst_port_name'] == vnf_dst_interface: # Fix: we might also get interface names, e.g, from a son-emu-cli call
386 # found the right link and connected switch
387 dst_sw = connected_sw
388 dst_sw_outport_nr = link_dict[link]['src_port_nr']
389 dst_sw_outport_name = link_dict[link]['src_port_name']
390 break
391
392 if not tag >= 0:
393 LOG.exception('tag not valid: {0}'.format(tag))
394
395 # get shortest path
396 try:
397 # returns the first found shortest path
398 # if all shortest paths are wanted, use: all_shortest_paths
399 path = nx.shortest_path(self.DCNetwork_graph, src_sw, dst_sw, weight=kwargs.get('weight'))
400 except:
401 LOG.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
402 vnf_src_name, vnf_dst_name, src_sw, dst_sw))
403 LOG.debug("Graph nodes: %r" % self.DCNetwork_graph.nodes())
404 LOG.debug("Graph edges: %r" % self.DCNetwork_graph.edges())
405 for e, v in self.DCNetwork_graph.edges():
406 LOG.debug("%r" % self.DCNetwork_graph[e][v])
407 return "No path could be found between {0} and {1}".format(vnf_src_name, vnf_dst_name)
408
409 LOG.info("Path between {0} and {1}: {2}".format(vnf_src_name, vnf_dst_name, path))
410
411 current_hop = src_sw
412 switch_inport_nr = src_sw_inport_nr
413
414 cmd = kwargs.get('cmd')
415
416 #iterate through the path to install the flow-entries
417 for i in range(0,len(path)):
418 current_node = self.getNodeByName(current_hop)
419
420 if path.index(current_hop) < len(path)-1:
421 next_hop = path[path.index(current_hop)+1]
422 else:
423 #last switch reached
424 next_hop = vnf_dst_name
425
426 next_node = self.getNodeByName(next_hop)
427
428 if next_hop == vnf_dst_name:
429 switch_outport_nr = dst_sw_outport_nr
430 LOG.info("end node reached: {0}".format(vnf_dst_name))
431 elif not isinstance( next_node, OVSSwitch ):
432 LOG.info("Next node: {0} is not a switch".format(next_hop))
433 return "Next node: {0} is not a switch".format(next_hop)
434 else:
435 # take first link between switches by default
436 index_edge_out = 0
437 switch_outport_nr = self.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100438
439
stevenvanrossembecc7c52016-11-07 05:52:01 +0100440 # set of entry via ovs-ofctl
441 if isinstance( current_node, OVSSwitch ):
442 kwargs['vlan'] = tag
443 kwargs['path'] = path
444 kwargs['current_hop'] = current_hop
445 kwargs['switch_inport_name'] = src_sw_inport_name
446 kwargs['switch_outport_name'] = dst_sw_outport_name
447 kwargs['skip_vlan_tag'] = True
448
449 monitor_placement = kwargs.get('monitor_placement')
450 # put monitor flow at the dst switch
451 insert_flow = False
452 if monitor_placement == 'tx' and path.index(current_hop) == 0: # first node:
453 insert_flow = True
454 # put monitoring flow at the src switch
455 elif monitor_placement == 'rx' and path.index(current_hop) == len(path) - 1: # last node:
456 insert_flow = True
457 else:
458 LOG.exception('invalid monitor command: {0}'.format(monitor_placement))
459
460
461 if self.controller == RemoteController and insert_flow:
462 ## set flow entry via ryu rest api
463 self._set_flow_entry_ryu_rest(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
464 break
465 elif insert_flow:
466 ## set flow entry via ovs-ofctl
467 self._set_flow_entry_dpctl(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
468 break
469
470 # take first link between switches by default
471 if isinstance( next_node, OVSSwitch ):
472 switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
473 current_hop = next_hop
474
475 return "path {2} between {0} and {1}".format(vnf_src_name, vnf_dst_name, cmd)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100476
477
stevenvanrossem461941c2016-05-10 11:41:29 +0200478 def setChain(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200479 """
480 Chain 2 vnf interfaces together by installing the flowrules in the switches along their path.
481 Currently the path is found using the default networkx shortest path function.
482 Each chain gets a unique vlan id , so different chains wil not interfere.
483
484 :param vnf_src_name: vnf name (string)
485 :param vnf_dst_name: vnf name (string)
486 :param vnf_src_interface: source interface name (string)
487 :param vnf_dst_interface: destination interface name (string)
488 :param cmd: 'add-flow' (default) to add a chain, 'del-flows' to remove a chain
489 :param cookie: cookie for the installed flowrules (can be used later as identifier for a set of installed chains)
490 :param match: custom match entry to be added to the flowrules (default: only in_port and vlan tag)
491 :param priority: custom flowrule priority
492 :return: output log string
493 """
stevenvanrossembecc7c52016-11-07 05:52:01 +0100494
495 # special procedure for monitoring flows
496 if kwargs.get('monitor'):
497
498 # check if chain already exists
499 found_chains = [chain_dict for chain_dict in self.installed_chains if
500 (chain_dict['vnf_src_name'] == vnf_src_name and chain_dict['vnf_src_interface'] == vnf_src_interface
501 and chain_dict['vnf_dst_name'] == vnf_dst_name and chain_dict['vnf_dst_interface'] == vnf_dst_interface)]
502
503 if len(found_chains) > 0:
504 # this chain exists, so need an extra monitoring flow
505 # assume only 1 chain per vnf/interface pair
506 LOG.debug('*** installing monitoring chain on top of pre-defined chain from {0}:{1} -> {2}:{3}'.
507 format(vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface))
508 tag = found_chains[0]['tag']
509 ret = self._addMonitorFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface,
510 tag=tag, table_id=0, **kwargs)
511 return ret
512 else:
513 # no chain existing (or E-LAN) -> install normal chain
514 LOG.warning('*** installing monitoring chain without pre-defined chain from {0}:{1} -> {2}:{3}'.
515 format(vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface))
516 pass
517
518
stevenvanrossem461941c2016-05-10 11:41:29 +0200519 cmd = kwargs.get('cmd')
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100520 if cmd == 'add-flow' or cmd == 'del-flows':
stevenvanrossem461941c2016-05-10 11:41:29 +0200521 ret = self._chainAddFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface, **kwargs)
522 if kwargs.get('bidirectional'):
stevenvanrossem81955a52016-05-12 14:34:12 +0200523 ret = ret +'\n' + self._chainAddFlow(vnf_dst_name, vnf_src_name, vnf_dst_interface, vnf_src_interface, **kwargs)
stevenvanrossem9315da42016-04-11 12:10:06 +0200524
stevenvanrossem461941c2016-05-10 11:41:29 +0200525 else:
stevenvanrossem81955a52016-05-12 14:34:12 +0200526 ret = "Command unknown"
527
528 return ret
stevenvanrossem461941c2016-05-10 11:41:29 +0200529
530
531 def _chainAddFlow(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
532
peusterm53d3c142016-07-18 10:10:11 +0200533 src_sw = None
peusterm53d3c142016-07-18 10:10:11 +0200534 src_sw_inport_nr = 0
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100535 src_sw_inport_name = None
536 dst_sw = None
peusterm53d3c142016-07-18 10:10:11 +0200537 dst_sw_outport_nr = 0
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100538 dst_sw_outport_name = None
peusterm53d3c142016-07-18 10:10:11 +0200539
540 LOG.debug("call chainAddFlow vnf_src_name=%r, vnf_src_interface=%r, vnf_dst_name=%r, vnf_dst_interface=%r",
541 vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface)
542
stevenvanrossem9315da42016-04-11 12:10:06 +0200543 #check if port is specified (vnf:port)
stevenvanrossemed711fd2016-04-11 16:59:29 +0200544 if vnf_src_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200545 # take first interface by default
546 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
547 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200548 vnf_src_interface = link_dict[0]['src_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200549
stevenvanrossem9315da42016-04-11 12:10:06 +0200550 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
551 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
552 for link in link_dict:
peusterm53d3c142016-07-18 10:10:11 +0200553 if (link_dict[link]['src_port_id'] == vnf_src_interface or
554 link_dict[link]['src_port_name'] == vnf_src_interface): # Fix: we might also get interface names, e.g, from a son-emu-cli call
stevenvanrossem9315da42016-04-11 12:10:06 +0200555 # found the right link and connected switch
stevenvanrossem9315da42016-04-11 12:10:06 +0200556 src_sw = connected_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200557 src_sw_inport_nr = link_dict[link]['dst_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100558 src_sw_inport_name = link_dict[link]['dst_port_name']
stevenvanrossem9315da42016-04-11 12:10:06 +0200559 break
560
stevenvanrossemed711fd2016-04-11 16:59:29 +0200561 if vnf_dst_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200562 # take first interface by default
563 connected_sw = self.DCNetwork_graph.neighbors(vnf_dst_name)[0]
564 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200565 vnf_dst_interface = link_dict[0]['dst_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200566
567 vnf_dst_name = vnf_dst_name.split(':')[0]
568 for connected_sw in self.DCNetwork_graph.neighbors(vnf_dst_name):
569 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
570 for link in link_dict:
peusterm53d3c142016-07-18 10:10:11 +0200571 if link_dict[link]['dst_port_id'] == vnf_dst_interface or \
572 link_dict[link]['dst_port_name'] == vnf_dst_interface: # Fix: we might also get interface names, e.g, from a son-emu-cli call
stevenvanrossem9315da42016-04-11 12:10:06 +0200573 # found the right link and connected switch
574 dst_sw = connected_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200575 dst_sw_outport_nr = link_dict[link]['src_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100576 dst_sw_outport_name = link_dict[link]['src_port_name']
stevenvanrossem9315da42016-04-11 12:10:06 +0200577 break
578
579
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100580 # get shortest path
stevenvanrossem9315da42016-04-11 12:10:06 +0200581 try:
stevenvanrossem1f68afb2016-05-02 15:36:04 +0200582 # returns the first found shortest path
583 # if all shortest paths are wanted, use: all_shortest_paths
stevenvanrossem461941c2016-05-10 11:41:29 +0200584 path = nx.shortest_path(self.DCNetwork_graph, src_sw, dst_sw, weight=kwargs.get('weight'))
stevenvanrossem9315da42016-04-11 12:10:06 +0200585 except:
peusterm53d3c142016-07-18 10:10:11 +0200586 LOG.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
587 vnf_src_name, vnf_dst_name, src_sw, dst_sw))
peustermf9a817d2016-07-18 09:06:04 +0200588 LOG.debug("Graph nodes: %r" % self.DCNetwork_graph.nodes())
589 LOG.debug("Graph edges: %r" % self.DCNetwork_graph.edges())
peusterm53d3c142016-07-18 10:10:11 +0200590 for e, v in self.DCNetwork_graph.edges():
591 LOG.debug("%r" % self.DCNetwork_graph[e][v])
stevenvanrossem9315da42016-04-11 12:10:06 +0200592 return "No path could be found between {0} and {1}".format(vnf_src_name, vnf_dst_name)
593
peustermf9a817d2016-07-18 09:06:04 +0200594 LOG.info("Path between {0} and {1}: {2}".format(vnf_src_name, vnf_dst_name, path))
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100595
stevenvanrossem9315da42016-04-11 12:10:06 +0200596 current_hop = src_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200597 switch_inport_nr = src_sw_inport_nr
stevenvanrossem9315da42016-04-11 12:10:06 +0200598
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100599 # choose free vlan
600 ## if path contains more than 1 switch
stevenvanrossem27b6d952016-05-10 16:37:57 +0200601 cmd = kwargs.get('cmd')
602 vlan = None
603 if cmd == 'add-flow':
stevenvanrossembecc7c52016-11-07 05:52:01 +0100604 if kwargs.get('tag'):
605 # use pre-defined tag
606 vlan = kwargs.get('tag')
607 else:
608 vlan = self.vlans.pop()
stevenvanrossem461941c2016-05-10 11:41:29 +0200609
stevenvanrossembecc7c52016-11-07 05:52:01 +0100610 # store the used vlan tag to identify this chain
611 if not kwargs.get('monitor'):
612 chain_dict = {}
613 chain_dict['vnf_src_name'] = vnf_src_name
614 chain_dict['vnf_dst_name'] = vnf_dst_name
615 chain_dict['vnf_src_interface'] = vnf_src_interface
616 chain_dict['vnf_dst_interface'] = vnf_dst_interface
617 chain_dict['tag'] = vlan
618 self.installed_chains.append(chain_dict)
619
620 #iterate through the path to install the flow-entries
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100621 for i in range(0,len(path)):
stevenvanrossem9315da42016-04-11 12:10:06 +0200622 current_node = self.getNodeByName(current_hop)
stevenvanrossem461941c2016-05-10 11:41:29 +0200623
stevenvanrossem9315da42016-04-11 12:10:06 +0200624 if path.index(current_hop) < len(path)-1:
625 next_hop = path[path.index(current_hop)+1]
626 else:
627 #last switch reached
628 next_hop = vnf_dst_name
629
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100630 next_node = self.getNodeByName(next_hop)
631
632 if next_hop == vnf_dst_name:
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200633 switch_outport_nr = dst_sw_outport_nr
peustermf9a817d2016-07-18 09:06:04 +0200634 LOG.info("end node reached: {0}".format(vnf_dst_name))
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100635 elif not isinstance( next_node, OVSSwitch ):
peustermf9a817d2016-07-18 09:06:04 +0200636 LOG.info("Next node: {0} is not a switch".format(next_hop))
stevenvanrossemeefea6c2016-02-17 12:03:26 +0100637 return "Next node: {0} is not a switch".format(next_hop)
stevenvanrossem9315da42016-04-11 12:10:06 +0200638 else:
stevenvanrossemed711fd2016-04-11 16:59:29 +0200639 # take first link between switches by default
stevenvanrossem9315da42016-04-11 12:10:06 +0200640 index_edge_out = 0
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200641 switch_outport_nr = self.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
stevenvanrossem9315da42016-04-11 12:10:06 +0200642
stevenvanrossem9315da42016-04-11 12:10:06 +0200643
stevenvanrossem461941c2016-05-10 11:41:29 +0200644 # set of entry via ovs-ofctl
stevenvanrossem9315da42016-04-11 12:10:06 +0200645 if isinstance( current_node, OVSSwitch ):
stevenvanrossem461941c2016-05-10 11:41:29 +0200646 kwargs['vlan'] = vlan
647 kwargs['path'] = path
648 kwargs['current_hop'] = current_hop
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100649 kwargs['switch_inport_name'] = src_sw_inport_name
650 kwargs['switch_outport_name'] = dst_sw_outport_name
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200651
652 if self.controller == RemoteController:
653 ## set flow entry via ryu rest api
654 self._set_flow_entry_ryu_rest(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
655 else:
656 ## set flow entry via ovs-ofctl
657 self._set_flow_entry_dpctl(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
658
stevenvanrossemed711fd2016-04-11 16:59:29 +0200659 # take first link between switches by default
660 if isinstance( next_node, OVSSwitch ):
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200661 switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
stevenvanrossemed711fd2016-04-11 16:59:29 +0200662 current_hop = next_hop
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100663
stevenvanrossem27b6d952016-05-10 16:37:57 +0200664 return "path {2} between {0} and {1}".format(vnf_src_name, vnf_dst_name, cmd)
665
666 def _set_flow_entry_ryu_rest(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
667 match = 'in_port=%s' % switch_inport_nr
668
669 cookie = kwargs.get('cookie')
670 match_input = kwargs.get('match')
671 cmd = kwargs.get('cmd')
672 path = kwargs.get('path')
673 current_hop = kwargs.get('current_hop')
674 vlan = kwargs.get('vlan')
stevenvanrossem61699eb2016-08-05 15:57:59 +0200675 priority = kwargs.get('priority')
stevenvanrossembecc7c52016-11-07 05:52:01 +0100676 # flag to not set the ovs port vlan tag
677 skip_vlan_tag = kwargs.get('skip_vlan_tag')
678 # table id to put this flowentry
679 table_id = kwargs.get('table_id')
680 if not table_id:
681 table_id = 0
stevenvanrossem27b6d952016-05-10 16:37:57 +0200682
683 s = ','
684 if match_input:
685 match = s.join([match, match_input])
686
687 flow = {}
688 flow['dpid'] = int(node.dpid, 16)
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200689
stevenvanrossem27b6d952016-05-10 16:37:57 +0200690 if cookie:
691 flow['cookie'] = int(cookie)
stevenvanrossem61699eb2016-08-05 15:57:59 +0200692 if priority:
693 flow['priority'] = int(priority)
stevenvanrossem27b6d952016-05-10 16:37:57 +0200694
stevenvanrossembecc7c52016-11-07 05:52:01 +0100695 flow['table_id'] = table_id
696
stevenvanrossem27b6d952016-05-10 16:37:57 +0200697 flow['actions'] = []
698
699 # possible Ryu actions, match fields:
700 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#add-a-flow-entry
701 if cmd == 'add-flow':
702 prefix = 'stats/flowentry/add'
stevenvanrossem27b6d952016-05-10 16:37:57 +0200703 if vlan != None:
704 if path.index(current_hop) == 0: # first node
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100705 # set vlan tag in ovs instance (to isolate E-LANs)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100706 if not skip_vlan_tag:
707 in_port_name = kwargs.get('switch_inport_name')
708 self._set_vlan_tag(node, in_port_name, vlan)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100709 # set vlan push action if more than 1 switch in the path
710 if len(path) > 1:
711 action = {}
712 action['type'] = 'PUSH_VLAN' # Push a new VLAN tag if a input frame is non-VLAN-tagged
713 action['ethertype'] = 33024 # Ethertype 0x8100(=33024): IEEE 802.1Q VLAN-tagged frame
714 flow['actions'].append(action)
715 action = {}
716 action['type'] = 'SET_FIELD'
717 action['field'] = 'vlan_vid'
718 action['value'] = vlan
719 flow['actions'].append(action)
720
721 if path.index(current_hop) == len(path) - 1: # last node
722 # set vlan tag in ovs instance (to isolate E-LANs)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100723 if not skip_vlan_tag:
724 out_port_name = kwargs.get('switch_outport_name')
725 self._set_vlan_tag(node, out_port_name, vlan)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100726 # set vlan pop action if more than 1 switch in the path
727 if len(path) > 1:
728 match += ',dl_vlan=%s' % vlan
729 action = {}
730 action['type'] = 'POP_VLAN'
731 flow['actions'].append(action)
732
733 if 0 < path.index(current_hop) < (len(path) - 1): # middle nodes
stevenvanrossem27b6d952016-05-10 16:37:57 +0200734 match += ',dl_vlan=%s' % vlan
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100735
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200736 # output action must come last
737 action = {}
738 action['type'] = 'OUTPUT'
739 action['port'] = switch_outport_nr
740 flow['actions'].append(action)
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200741
stevenvanrossem27b6d952016-05-10 16:37:57 +0200742 elif cmd == 'del-flows':
stevenvanrossem27b6d952016-05-10 16:37:57 +0200743 prefix = 'stats/flowentry/delete'
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200744
stevenvanrossem27b6d952016-05-10 16:37:57 +0200745 if cookie:
stevenvanrossem1ef77022016-05-12 16:36:10 +0200746 # TODO: add cookie_mask as argument
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200747 flow['cookie_mask'] = int('0xffffffffffffffff', 16) # need full mask to match complete cookie
stevenvanrossem27b6d952016-05-10 16:37:57 +0200748
749 action = {}
750 action['type'] = 'OUTPUT'
751 action['port'] = switch_outport_nr
752 flow['actions'].append(action)
753
754 flow['match'] = self._parse_match(match)
755 self.ryu_REST(prefix, data=flow)
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100756
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100757 def _set_vlan_tag(self, node, switch_port, tag):
758 node.vsctl('set', 'port {0} tag={1}'.format(switch_port,tag))
759 LOG.debug("set vlan in switch: {0} in_port: {1} vlan tag: {2}".format(node.name, switch_port, tag))
760
stevenvanrossem461941c2016-05-10 11:41:29 +0200761 def _set_flow_entry_dpctl(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100762
stevenvanrossem23c48092016-05-06 17:21:12 +0200763 match = 'in_port=%s' % switch_inport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200764
765 cookie = kwargs.get('cookie')
766 match_input = kwargs.get('match')
767 cmd = kwargs.get('cmd')
768 path = kwargs.get('path')
769 current_hop = kwargs.get('current_hop')
770 vlan = kwargs.get('vlan')
771
stevenvanrossem898a2af2016-05-06 18:28:57 +0200772 s = ','
773 if cookie:
774 cookie = 'cookie=%s' % cookie
775 match = s.join([cookie, match])
stevenvanrossem23c48092016-05-06 17:21:12 +0200776 if match_input:
stevenvanrossem23c48092016-05-06 17:21:12 +0200777 match = s.join([match, match_input])
stevenvanrossem23c48092016-05-06 17:21:12 +0200778 if cmd == 'add-flow':
779 action = 'action=%s' % switch_outport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200780 if vlan != None:
781 if path.index(current_hop) == 0: # first node
782 action = ('action=mod_vlan_vid:%s' % vlan) + (',output=%s' % switch_outport_nr)
783 match = '-O OpenFlow13 ' + match
784 elif path.index(current_hop) == len(path) - 1: # last node
785 match += ',dl_vlan=%s' % vlan
786 action = 'action=strip_vlan,output=%s' % switch_outport_nr
787 else: # middle nodes
788 match += ',dl_vlan=%s' % vlan
stevenvanrossem23c48092016-05-06 17:21:12 +0200789 ofcmd = s.join([match, action])
790 elif cmd == 'del-flows':
791 ofcmd = match
792 else:
793 ofcmd = ''
794
795 node.dpctl(cmd, ofcmd)
peustermf9a817d2016-07-18 09:06:04 +0200796 LOG.info("{3} in switch: {0} in_port: {1} out_port: {2}".format(node.name, switch_inport_nr,
stevenvanrossem461941c2016-05-10 11:41:29 +0200797 switch_outport_nr, cmd))
stevenvanrossem23c48092016-05-06 17:21:12 +0200798
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100799 # start Ryu Openflow controller as Remote Controller for the DCNetwork
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200800 def startRyu(self, learning_switch=True):
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100801 # start Ryu controller with rest-API
802 python_install_path = site.getsitepackages()[0]
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100803 # ryu default learning switch
804 #ryu_path = python_install_path + '/ryu/app/simple_switch_13.py'
805 #custom learning switch that installs a default NORMAL action in the ovs switches
806 dir_path = os.path.dirname(os.path.realpath(__file__))
807 ryu_path = dir_path + '/son_emu_simple_switch_13.py'
peustermde14f332016-03-15 16:14:21 +0100808 ryu_path2 = python_install_path + '/ryu/app/ofctl_rest.py'
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100809 # change the default Openflow controller port to 6653 (official IANA-assigned port number), as used by Mininet
810 # Ryu still uses 6633 as default
811 ryu_option = '--ofp-tcp-listen-port'
812 ryu_of_port = '6653'
peustermde14f332016-03-15 16:14:21 +0100813 ryu_cmd = 'ryu-manager'
peustermef6629e2016-03-14 17:21:56 +0100814 FNULL = open("/tmp/ryu.log", 'w')
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200815 if learning_switch:
816 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 +0100817 LOG.debug('starting ryu-controller with {0}'.format(ryu_path))
818 LOG.debug('starting ryu-controller with {0}'.format(ryu_path2))
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200819 else:
stevenvanrossem73efd192016-06-29 01:44:07 +0200820 # no learning switch, but with rest api
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200821 self.ryu_process = Popen([ryu_cmd, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100822 LOG.debug('starting ryu-controller with {0}'.format(ryu_path2))
peusterm391773a2016-03-14 17:40:43 +0100823 time.sleep(1)
824
peusterm8b04b532016-07-19 16:55:38 +0200825 def killRyu(self):
826 """
827 Stop the Ryu controller that might be started by son-emu.
828 :return:
829 """
830 # try it nicely
peustermde14f332016-03-15 16:14:21 +0100831 if self.ryu_process is not None:
peusterm391773a2016-03-14 17:40:43 +0100832 self.ryu_process.terminate()
833 self.ryu_process.kill()
peusterm8b04b532016-07-19 16:55:38 +0200834 # ensure its death ;-)
stevenvanrossem89706802016-07-19 02:54:45 +0200835 Popen(['pkill', '-f', 'ryu-manager'])
peusterm391773a2016-03-14 17:40:43 +0100836
stevenvanrossem27b6d952016-05-10 16:37:57 +0200837 def ryu_REST(self, prefix, dpid=None, data=None):
stevenvanrossem27b6d952016-05-10 16:37:57 +0200838
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200839 if dpid:
840 url = self.ryu_REST_api + '/' + str(prefix) + '/' + str(dpid)
841 else:
842 url = self.ryu_REST_api + '/' + str(prefix)
843 if data:
844 req = self.RyuSession.post(url, json=data)
845 else:
846 req = self.RyuSession.get(url)
847
848
849 # do extra logging if status code is not 200 (OK)
850 if req.status_code is not requests.codes.ok:
851 logging.info(
852 'type {0} encoding: {1} text: {2} headers: {3} history: {4}'.format(req.headers['content-type'],
853 req.encoding, req.text,
854 req.headers, req.history))
855 LOG.info('url: {0}'.format(str(url)))
856 if data: LOG.info('POST: {0}'.format(str(data)))
857 LOG.info('status: {0} reason: {1}'.format(req.status_code, req.reason))
858
859
860 if 'json' in req.headers['content-type']:
861 ret = req.json()
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200862 return ret
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200863
864 ret = req.text.rstrip()
865 return ret
866
stevenvanrossem27b6d952016-05-10 16:37:57 +0200867
868 # need to respect that some match fields must be integers
869 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#description-of-match-and-actions
870 def _parse_match(self, match):
871 matches = match.split(',')
872 dict = {}
873 for m in matches:
874 match = m.split('=')
875 if len(match) == 2:
876 try:
877 m2 = int(match[1], 0)
878 except:
879 m2 = match[1]
880
881 dict.update({match[0]:m2})
882 return dict
883
stevenvanrossem566779d2016-11-07 06:33:44 +0100884 def find_connected_dc_interface(self, vnf_src_name, vnf_src_interface):
885 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
886 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
887 for link in link_dict:
888 if (link_dict[link]['src_port_id'] == vnf_src_interface or
889 link_dict[link]['src_port_name'] == vnf_src_interface): # Fix: we might also get interface names, e.g, from a son-emu-cli call
890 # found the right link and connected switch
891 src_sw = connected_sw
892 src_sw_inport_nr = link_dict[link]['dst_port_nr']
893 src_sw_inport_name = link_dict[link]['dst_port_name']
894 return src_sw_inport_name