blob: 418ab208e213e92e7e8cda1f24d61a04f653363f [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)
joka27edb4f2017-01-17 12:40:59 +0100205 match = re.search('([0-9]*\.?[0-9]+)', str(params[attr]))
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200206 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
stevenvanrossem00e65b92017-04-18 16:57:40 +0200232 def removeLink(self, link=None, node1=None, node2=None):
233 """
234 Remove the link from the Containernet and the networkx graph
235 """
236 Containernet.removeLink(self, link=link, node1=node1, node2=node2)
237 self.DCNetwork_graph.remove_edge(node2.name, node1.name)
238
peusterma47db032016-02-04 14:55:29 +0100239 def addDocker( self, label, **params ):
peusterm5b844a12016-01-11 15:58:15 +0100240 """
peusterm293cbc32016-01-13 17:05:28 +0100241 Wrapper for addDocker method to use custom container class.
peusterm5b844a12016-01-11 15:58:15 +0100242 """
stevenvanrossem8fbf9782016-02-17 11:40:23 +0100243 self.DCNetwork_graph.add_node(label)
peusterm5877ea22016-05-11 13:44:59 +0200244 return Containernet.addDocker(self, label, cls=EmulatorCompute, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100245
stevenvanrossem8fbf9782016-02-17 11:40:23 +0100246 def removeDocker( self, label, **params ):
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100247 """
248 Wrapper for removeDocker method to update graph.
249 """
stevenvanrossem8fbf9782016-02-17 11:40:23 +0100250 self.DCNetwork_graph.remove_node(label)
peusterm5877ea22016-05-11 13:44:59 +0200251 return Containernet.removeDocker(self, label, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100252
253 def addSwitch( self, name, add_to_graph=True, **params ):
254 """
255 Wrapper for addSwitch method to store switch also in graph.
256 """
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100257
258 # add this switch to the global topology overview
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100259 if add_to_graph:
260 self.DCNetwork_graph.add_node(name)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100261
262 # set the learning switch behavior
263 if 'failMode' in params :
264 failMode = params['failMode']
265 else :
266 failMode = self.failMode
267
268 s = Containernet.addSwitch(self, name, protocols='OpenFlow10,OpenFlow12,OpenFlow13', failMode=failMode, **params)
269
270 # set flow entry that enables learning switch behavior (needed to enable E-LAN functionality)
271 #LOG.info('failmode {0}'.format(failMode))
272 #if failMode == 'standalone' :
273 # LOG.info('add NORMAL')
274 # s.dpctl('add-flow', 'actions=NORMAL')
275
276 return s
peustermc3b977e2016-01-12 10:09:35 +0100277
peustermbd44f4a2016-01-13 14:53:30 +0100278 def getAllContainers(self):
279 """
280 Returns a list with all containers within all data centers.
281 """
282 all_containers = []
283 for dc in self.dcs.itervalues():
284 all_containers += dc.listCompute()
285 return all_containers
286
peustermcbcd4c22015-12-28 11:33:42 +0100287 def start(self):
288 # start
289 for dc in self.dcs.itervalues():
290 dc.start()
peusterm5877ea22016-05-11 13:44:59 +0200291 Containernet.start(self)
peustermcbcd4c22015-12-28 11:33:42 +0100292
293 def stop(self):
stevenvanrossem60670da2016-04-15 15:31:28 +0200294
stevenvanrossemc6abf132016-04-14 11:15:58 +0200295 # stop the monitor agent
stevenvanrossem60670da2016-04-15 15:31:28 +0200296 if self.monitor_agent is not None:
297 self.monitor_agent.stop()
peustermcbcd4c22015-12-28 11:33:42 +0100298
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200299 # stop emulator net
peusterm5877ea22016-05-11 13:44:59 +0200300 Containernet.stop(self)
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200301
302 # stop Ryu controller
peusterm8b04b532016-07-19 16:55:38 +0200303 self.killRyu()
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200304
305
peustermcbcd4c22015-12-28 11:33:42 +0100306 def CLI(self):
peusterm293cbc32016-01-13 17:05:28 +0100307 CLI(self)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100308
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100309 def setLAN(self, vnf_list):
310 """
311 setup an E-LAN network by assigning the same VLAN tag to each DC interface of the VNFs in the E-LAN
312
313 :param vnf_list: names of the VNFs in this E-LAN [{name:,interface:},...]
314 :return:
315 """
316 src_sw = None
317 src_sw_inport_nr = 0
318 src_sw_inport_name = None
319
320 # get a vlan tag for this E-LAN
321 vlan = self.vlans.pop()
322
323 for vnf in vnf_list:
324 vnf_src_name = vnf['name']
325 vnf_src_interface = vnf['interface']
326
327 # check if port is specified (vnf:port)
328 if vnf_src_interface is None:
329 # take first interface by default
330 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
331 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
332 vnf_src_interface = link_dict[0]['src_port_id']
333
334 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
335 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
336 for link in link_dict:
337 if (link_dict[link]['src_port_id'] == vnf_src_interface or
338 link_dict[link]['src_port_name'] == vnf_src_interface): # Fix: we might also get interface names, e.g, from a son-emu-cli call
339 # found the right link and connected switch
340 src_sw = connected_sw
341 src_sw_inport_nr = link_dict[link]['dst_port_nr']
342 src_sw_inport_name = link_dict[link]['dst_port_name']
343 break
344
345 # set the tag on the dc switch interface
346 LOG.debug('set E-LAN: vnf name: {0} interface: {1} tag: {2}'.format(vnf_src_name, vnf_src_interface,vlan))
347 switch_node = self.getNodeByName(src_sw)
348 self._set_vlan_tag(switch_node, src_sw_inport_name, vlan)
349
stevenvanrossembecc7c52016-11-07 05:52:01 +0100350 def _addMonitorFlow(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None,
351 tag=None, **kwargs):
stevenvanrossembf1754e2016-11-17 10:20:52 +0100352 """
353 Add a monitoring flow entry that adds a special flowentry/counter at the begin or end of a chain.
354 So this monitoring flowrule exists on top of a previously defined chain rule and uses the same vlan tag/routing.
355 :param vnf_src_name:
356 :param vnf_dst_name:
357 :param vnf_src_interface:
358 :param vnf_dst_interface:
359 :param tag: vlan tag to be used for this chain (same tag as existing chain)
360 :param monitor_placement: 'tx' or 'rx' indicating to place the extra flowentry resp. at the beginning or end of the chain
361 :return:
362 """
stevenvanrossembecc7c52016-11-07 05:52:01 +0100363
364 src_sw = None
365 src_sw_inport_nr = 0
366 src_sw_inport_name = None
367 dst_sw = None
368 dst_sw_outport_nr = 0
369 dst_sw_outport_name = None
370
371 LOG.debug("call AddMonitorFlow vnf_src_name=%r, vnf_src_interface=%r, vnf_dst_name=%r, vnf_dst_interface=%r",
372 vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface)
373
374 #check if port is specified (vnf:port)
375 if vnf_src_interface is None:
376 # take first interface by default
377 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
378 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
379 vnf_src_interface = link_dict[0]['src_port_id']
380
381 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
382 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
383 for link in link_dict:
384 if (link_dict[link]['src_port_id'] == vnf_src_interface or
385 link_dict[link]['src_port_name'] == vnf_src_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 src_sw = connected_sw
388 src_sw_inport_nr = link_dict[link]['dst_port_nr']
389 src_sw_inport_name = link_dict[link]['dst_port_name']
390 break
391
392 if vnf_dst_interface is None:
393 # take first interface by default
394 connected_sw = self.DCNetwork_graph.neighbors(vnf_dst_name)[0]
395 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
396 vnf_dst_interface = link_dict[0]['dst_port_id']
397
398 vnf_dst_name = vnf_dst_name.split(':')[0]
399 for connected_sw in self.DCNetwork_graph.neighbors(vnf_dst_name):
400 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
401 for link in link_dict:
402 if link_dict[link]['dst_port_id'] == vnf_dst_interface or \
403 link_dict[link]['dst_port_name'] == vnf_dst_interface: # Fix: we might also get interface names, e.g, from a son-emu-cli call
404 # found the right link and connected switch
405 dst_sw = connected_sw
406 dst_sw_outport_nr = link_dict[link]['src_port_nr']
407 dst_sw_outport_name = link_dict[link]['src_port_name']
408 break
409
410 if not tag >= 0:
411 LOG.exception('tag not valid: {0}'.format(tag))
412
413 # get shortest path
414 try:
415 # returns the first found shortest path
416 # if all shortest paths are wanted, use: all_shortest_paths
417 path = nx.shortest_path(self.DCNetwork_graph, src_sw, dst_sw, weight=kwargs.get('weight'))
418 except:
419 LOG.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
420 vnf_src_name, vnf_dst_name, src_sw, dst_sw))
421 LOG.debug("Graph nodes: %r" % self.DCNetwork_graph.nodes())
422 LOG.debug("Graph edges: %r" % self.DCNetwork_graph.edges())
423 for e, v in self.DCNetwork_graph.edges():
424 LOG.debug("%r" % self.DCNetwork_graph[e][v])
425 return "No path could be found between {0} and {1}".format(vnf_src_name, vnf_dst_name)
426
427 LOG.info("Path between {0} and {1}: {2}".format(vnf_src_name, vnf_dst_name, path))
428
429 current_hop = src_sw
430 switch_inport_nr = src_sw_inport_nr
431
432 cmd = kwargs.get('cmd')
433
434 #iterate through the path to install the flow-entries
435 for i in range(0,len(path)):
436 current_node = self.getNodeByName(current_hop)
437
438 if path.index(current_hop) < len(path)-1:
439 next_hop = path[path.index(current_hop)+1]
440 else:
441 #last switch reached
442 next_hop = vnf_dst_name
443
444 next_node = self.getNodeByName(next_hop)
445
446 if next_hop == vnf_dst_name:
447 switch_outport_nr = dst_sw_outport_nr
448 LOG.info("end node reached: {0}".format(vnf_dst_name))
449 elif not isinstance( next_node, OVSSwitch ):
450 LOG.info("Next node: {0} is not a switch".format(next_hop))
451 return "Next node: {0} is not a switch".format(next_hop)
452 else:
453 # take first link between switches by default
454 index_edge_out = 0
455 switch_outport_nr = self.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100456
457
stevenvanrossembecc7c52016-11-07 05:52:01 +0100458 # set of entry via ovs-ofctl
459 if isinstance( current_node, OVSSwitch ):
460 kwargs['vlan'] = tag
461 kwargs['path'] = path
462 kwargs['current_hop'] = current_hop
463 kwargs['switch_inport_name'] = src_sw_inport_name
464 kwargs['switch_outport_name'] = dst_sw_outport_name
465 kwargs['skip_vlan_tag'] = True
stevenvanrossem263eee52017-02-08 01:04:36 +0100466 kwargs['pathindex'] = i
stevenvanrossembecc7c52016-11-07 05:52:01 +0100467
stevenvanrossem4fac2af2016-12-22 01:26:02 +0100468 monitor_placement = kwargs.get('monitor_placement').strip()
stevenvanrossembecc7c52016-11-07 05:52:01 +0100469 # put monitor flow at the dst switch
470 insert_flow = False
471 if monitor_placement == 'tx' and path.index(current_hop) == 0: # first node:
472 insert_flow = True
473 # put monitoring flow at the src switch
474 elif monitor_placement == 'rx' and path.index(current_hop) == len(path) - 1: # last node:
475 insert_flow = True
stevenvanrossem4fac2af2016-12-22 01:26:02 +0100476 elif monitor_placement not in ['rx', 'tx']:
stevenvanrossembecc7c52016-11-07 05:52:01 +0100477 LOG.exception('invalid monitor command: {0}'.format(monitor_placement))
478
479
480 if self.controller == RemoteController and insert_flow:
481 ## set flow entry via ryu rest api
482 self._set_flow_entry_ryu_rest(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
483 break
484 elif insert_flow:
485 ## set flow entry via ovs-ofctl
486 self._set_flow_entry_dpctl(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
487 break
488
489 # take first link between switches by default
490 if isinstance( next_node, OVSSwitch ):
491 switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
492 current_hop = next_hop
493
494 return "path {2} between {0} and {1}".format(vnf_src_name, vnf_dst_name, cmd)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100495
496
stevenvanrossem461941c2016-05-10 11:41:29 +0200497 def setChain(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200498 """
499 Chain 2 vnf interfaces together by installing the flowrules in the switches along their path.
500 Currently the path is found using the default networkx shortest path function.
501 Each chain gets a unique vlan id , so different chains wil not interfere.
502
503 :param vnf_src_name: vnf name (string)
504 :param vnf_dst_name: vnf name (string)
505 :param vnf_src_interface: source interface name (string)
506 :param vnf_dst_interface: destination interface name (string)
507 :param cmd: 'add-flow' (default) to add a chain, 'del-flows' to remove a chain
508 :param cookie: cookie for the installed flowrules (can be used later as identifier for a set of installed chains)
509 :param match: custom match entry to be added to the flowrules (default: only in_port and vlan tag)
510 :param priority: custom flowrule priority
stevenvanrossembf1754e2016-11-17 10:20:52 +0100511 :param monitor: boolean to indicate whether this chain is a monitoring chain
512 :param tag: vlan tag to be used for this chain (pre-defined or new one if none is specified)
stevenvanrossem634c5ef2017-02-08 00:46:02 +0100513 :param skip_vlan_tag: boolean to indicate if a vlan tag should be appointed to this flow or not
jokac304ad32017-01-09 10:58:23 +0100514 :param path: custom path between the two VNFs (list of switches)
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200515 :return: output log string
516 """
stevenvanrossembecc7c52016-11-07 05:52:01 +0100517
518 # special procedure for monitoring flows
519 if kwargs.get('monitor'):
520
521 # check if chain already exists
522 found_chains = [chain_dict for chain_dict in self.installed_chains if
523 (chain_dict['vnf_src_name'] == vnf_src_name and chain_dict['vnf_src_interface'] == vnf_src_interface
524 and chain_dict['vnf_dst_name'] == vnf_dst_name and chain_dict['vnf_dst_interface'] == vnf_dst_interface)]
525
526 if len(found_chains) > 0:
527 # this chain exists, so need an extra monitoring flow
528 # assume only 1 chain per vnf/interface pair
529 LOG.debug('*** installing monitoring chain on top of pre-defined chain from {0}:{1} -> {2}:{3}'.
530 format(vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface))
531 tag = found_chains[0]['tag']
532 ret = self._addMonitorFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface,
533 tag=tag, table_id=0, **kwargs)
534 return ret
535 else:
536 # no chain existing (or E-LAN) -> install normal chain
stevenvanrossem4fac2af2016-12-22 01:26:02 +0100537 LOG.warning('*** installing monitoring chain without pre-defined NSD chain from {0}:{1} -> {2}:{3}'.
stevenvanrossembecc7c52016-11-07 05:52:01 +0100538 format(vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface))
539 pass
540
541
stevenvanrossem461941c2016-05-10 11:41:29 +0200542 cmd = kwargs.get('cmd')
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100543 if cmd == 'add-flow' or cmd == 'del-flows':
stevenvanrossem461941c2016-05-10 11:41:29 +0200544 ret = self._chainAddFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface, **kwargs)
545 if kwargs.get('bidirectional'):
jokac304ad32017-01-09 10:58:23 +0100546 if kwargs.get('path') is not None:
547 kwargs['path'] = list(reversed(kwargs.get('path')))
stevenvanrossem81955a52016-05-12 14:34:12 +0200548 ret = ret +'\n' + self._chainAddFlow(vnf_dst_name, vnf_src_name, vnf_dst_interface, vnf_src_interface, **kwargs)
stevenvanrossem9315da42016-04-11 12:10:06 +0200549
stevenvanrossem461941c2016-05-10 11:41:29 +0200550 else:
stevenvanrossem81955a52016-05-12 14:34:12 +0200551 ret = "Command unknown"
552
553 return ret
stevenvanrossem461941c2016-05-10 11:41:29 +0200554
555
556 def _chainAddFlow(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
557
peusterm53d3c142016-07-18 10:10:11 +0200558 src_sw = None
peusterm53d3c142016-07-18 10:10:11 +0200559 src_sw_inport_nr = 0
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100560 src_sw_inport_name = None
561 dst_sw = None
peusterm53d3c142016-07-18 10:10:11 +0200562 dst_sw_outport_nr = 0
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100563 dst_sw_outport_name = None
peusterm53d3c142016-07-18 10:10:11 +0200564
565 LOG.debug("call chainAddFlow vnf_src_name=%r, vnf_src_interface=%r, vnf_dst_name=%r, vnf_dst_interface=%r",
566 vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface)
567
stevenvanrossem9315da42016-04-11 12:10:06 +0200568 #check if port is specified (vnf:port)
stevenvanrossemed711fd2016-04-11 16:59:29 +0200569 if vnf_src_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200570 # take first interface by default
571 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
572 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200573 vnf_src_interface = link_dict[0]['src_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200574
stevenvanrossem9315da42016-04-11 12:10:06 +0200575 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
576 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
577 for link in link_dict:
peusterm53d3c142016-07-18 10:10:11 +0200578 if (link_dict[link]['src_port_id'] == vnf_src_interface or
579 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 +0200580 # found the right link and connected switch
stevenvanrossem9315da42016-04-11 12:10:06 +0200581 src_sw = connected_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200582 src_sw_inport_nr = link_dict[link]['dst_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100583 src_sw_inport_name = link_dict[link]['dst_port_name']
stevenvanrossem9315da42016-04-11 12:10:06 +0200584 break
585
stevenvanrossemed711fd2016-04-11 16:59:29 +0200586 if vnf_dst_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200587 # take first interface by default
588 connected_sw = self.DCNetwork_graph.neighbors(vnf_dst_name)[0]
589 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200590 vnf_dst_interface = link_dict[0]['dst_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200591
592 vnf_dst_name = vnf_dst_name.split(':')[0]
593 for connected_sw in self.DCNetwork_graph.neighbors(vnf_dst_name):
594 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
595 for link in link_dict:
peusterm53d3c142016-07-18 10:10:11 +0200596 if link_dict[link]['dst_port_id'] == vnf_dst_interface or \
597 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 +0200598 # found the right link and connected switch
599 dst_sw = connected_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200600 dst_sw_outport_nr = link_dict[link]['src_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100601 dst_sw_outport_name = link_dict[link]['src_port_name']
stevenvanrossem9315da42016-04-11 12:10:06 +0200602 break
603
jokac304ad32017-01-09 10:58:23 +0100604 path = kwargs.get('path')
605 if path is None:
606 # get shortest path
607 try:
608 # returns the first found shortest path
609 # if all shortest paths are wanted, use: all_shortest_paths
610 path = nx.shortest_path(self.DCNetwork_graph, src_sw, dst_sw, weight=kwargs.get('weight'))
611 except:
612 LOG.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
613 vnf_src_name, vnf_dst_name, src_sw, dst_sw))
614 LOG.debug("Graph nodes: %r" % self.DCNetwork_graph.nodes())
615 LOG.debug("Graph edges: %r" % self.DCNetwork_graph.edges())
616 for e, v in self.DCNetwork_graph.edges():
617 LOG.debug("%r" % self.DCNetwork_graph[e][v])
618 return "No path could be found between {0} and {1}".format(vnf_src_name, vnf_dst_name)
stevenvanrossem9315da42016-04-11 12:10:06 +0200619
peustermf9a817d2016-07-18 09:06:04 +0200620 LOG.info("Path between {0} and {1}: {2}".format(vnf_src_name, vnf_dst_name, path))
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100621
stevenvanrossem9315da42016-04-11 12:10:06 +0200622 current_hop = src_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200623 switch_inport_nr = src_sw_inport_nr
stevenvanrossem9315da42016-04-11 12:10:06 +0200624
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100625 # choose free vlan
stevenvanrossem27b6d952016-05-10 16:37:57 +0200626 cmd = kwargs.get('cmd')
627 vlan = None
stevenvanrossem263eee52017-02-08 01:04:36 +0100628 if cmd == 'add-flow':
stevenvanrossembecc7c52016-11-07 05:52:01 +0100629 if kwargs.get('tag'):
630 # use pre-defined tag
631 vlan = kwargs.get('tag')
632 else:
633 vlan = self.vlans.pop()
stevenvanrossem461941c2016-05-10 11:41:29 +0200634
stevenvanrossembecc7c52016-11-07 05:52:01 +0100635 # store the used vlan tag to identify this chain
636 if not kwargs.get('monitor'):
637 chain_dict = {}
638 chain_dict['vnf_src_name'] = vnf_src_name
639 chain_dict['vnf_dst_name'] = vnf_dst_name
640 chain_dict['vnf_src_interface'] = vnf_src_interface
641 chain_dict['vnf_dst_interface'] = vnf_dst_interface
642 chain_dict['tag'] = vlan
643 self.installed_chains.append(chain_dict)
644
645 #iterate through the path to install the flow-entries
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100646 for i in range(0,len(path)):
stevenvanrossem9315da42016-04-11 12:10:06 +0200647 current_node = self.getNodeByName(current_hop)
stevenvanrossem461941c2016-05-10 11:41:29 +0200648
jokac304ad32017-01-09 10:58:23 +0100649 if i < len(path) - 1:
650 next_hop = path[i + 1]
stevenvanrossem9315da42016-04-11 12:10:06 +0200651 else:
jokac304ad32017-01-09 10:58:23 +0100652 # last switch reached
stevenvanrossem9315da42016-04-11 12:10:06 +0200653 next_hop = vnf_dst_name
654
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100655 next_node = self.getNodeByName(next_hop)
656
657 if next_hop == vnf_dst_name:
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200658 switch_outport_nr = dst_sw_outport_nr
peustermf9a817d2016-07-18 09:06:04 +0200659 LOG.info("end node reached: {0}".format(vnf_dst_name))
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100660 elif not isinstance( next_node, OVSSwitch ):
peustermf9a817d2016-07-18 09:06:04 +0200661 LOG.info("Next node: {0} is not a switch".format(next_hop))
stevenvanrossemeefea6c2016-02-17 12:03:26 +0100662 return "Next node: {0} is not a switch".format(next_hop)
stevenvanrossem9315da42016-04-11 12:10:06 +0200663 else:
stevenvanrossemed711fd2016-04-11 16:59:29 +0200664 # take first link between switches by default
stevenvanrossem9315da42016-04-11 12:10:06 +0200665 index_edge_out = 0
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200666 switch_outport_nr = self.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
stevenvanrossem9315da42016-04-11 12:10:06 +0200667
stevenvanrossem9315da42016-04-11 12:10:06 +0200668
stevenvanrossem634c5ef2017-02-08 00:46:02 +0100669 # set OpenFlow entry
stevenvanrossem9315da42016-04-11 12:10:06 +0200670 if isinstance( current_node, OVSSwitch ):
stevenvanrossem461941c2016-05-10 11:41:29 +0200671 kwargs['vlan'] = vlan
672 kwargs['path'] = path
673 kwargs['current_hop'] = current_hop
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100674 kwargs['switch_inport_name'] = src_sw_inport_name
675 kwargs['switch_outport_name'] = dst_sw_outport_name
jokac304ad32017-01-09 10:58:23 +0100676 kwargs['pathindex'] = i
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200677
678 if self.controller == RemoteController:
679 ## set flow entry via ryu rest api
680 self._set_flow_entry_ryu_rest(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
681 else:
682 ## set flow entry via ovs-ofctl
683 self._set_flow_entry_dpctl(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
684
stevenvanrossemed711fd2016-04-11 16:59:29 +0200685 # take first link between switches by default
686 if isinstance( next_node, OVSSwitch ):
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200687 switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
stevenvanrossemed711fd2016-04-11 16:59:29 +0200688 current_hop = next_hop
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100689
stevenvanrossem27b6d952016-05-10 16:37:57 +0200690 return "path {2} between {0} and {1}".format(vnf_src_name, vnf_dst_name, cmd)
691
692 def _set_flow_entry_ryu_rest(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
693 match = 'in_port=%s' % switch_inport_nr
694
695 cookie = kwargs.get('cookie')
696 match_input = kwargs.get('match')
697 cmd = kwargs.get('cmd')
698 path = kwargs.get('path')
jokac304ad32017-01-09 10:58:23 +0100699 index = kwargs.get('pathindex')
700
stevenvanrossem27b6d952016-05-10 16:37:57 +0200701 vlan = kwargs.get('vlan')
stevenvanrossem61699eb2016-08-05 15:57:59 +0200702 priority = kwargs.get('priority')
stevenvanrossembecc7c52016-11-07 05:52:01 +0100703 # flag to not set the ovs port vlan tag
704 skip_vlan_tag = kwargs.get('skip_vlan_tag')
705 # table id to put this flowentry
706 table_id = kwargs.get('table_id')
707 if not table_id:
708 table_id = 0
stevenvanrossem27b6d952016-05-10 16:37:57 +0200709
710 s = ','
711 if match_input:
712 match = s.join([match, match_input])
713
714 flow = {}
715 flow['dpid'] = int(node.dpid, 16)
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200716
stevenvanrossem27b6d952016-05-10 16:37:57 +0200717 if cookie:
718 flow['cookie'] = int(cookie)
stevenvanrossem61699eb2016-08-05 15:57:59 +0200719 if priority:
720 flow['priority'] = int(priority)
stevenvanrossem27b6d952016-05-10 16:37:57 +0200721
stevenvanrossembecc7c52016-11-07 05:52:01 +0100722 flow['table_id'] = table_id
723
stevenvanrossem27b6d952016-05-10 16:37:57 +0200724 flow['actions'] = []
725
726 # possible Ryu actions, match fields:
727 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#add-a-flow-entry
728 if cmd == 'add-flow':
729 prefix = 'stats/flowentry/add'
stevenvanrossem27b6d952016-05-10 16:37:57 +0200730 if vlan != None:
jokac304ad32017-01-09 10:58:23 +0100731 if index == 0: # first node
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100732 # set vlan tag in ovs instance (to isolate E-LANs)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100733 if not skip_vlan_tag:
734 in_port_name = kwargs.get('switch_inport_name')
735 self._set_vlan_tag(node, in_port_name, vlan)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100736 # set vlan push action if more than 1 switch in the path
737 if len(path) > 1:
738 action = {}
739 action['type'] = 'PUSH_VLAN' # Push a new VLAN tag if a input frame is non-VLAN-tagged
740 action['ethertype'] = 33024 # Ethertype 0x8100(=33024): IEEE 802.1Q VLAN-tagged frame
741 flow['actions'].append(action)
742 action = {}
743 action['type'] = 'SET_FIELD'
744 action['field'] = 'vlan_vid'
stevenvanrossem9cc73602017-01-27 23:37:29 +0100745 # ryu expects the field to be masked
746 action['value'] = vlan | 0x1000
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100747 flow['actions'].append(action)
748
stevenvanrossem9cc73602017-01-27 23:37:29 +0100749 elif index == len(path) - 1: # last node
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100750 # set vlan tag in ovs instance (to isolate E-LANs)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100751 if not skip_vlan_tag:
752 out_port_name = kwargs.get('switch_outport_name')
753 self._set_vlan_tag(node, out_port_name, vlan)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100754 # set vlan pop action if more than 1 switch in the path
755 if len(path) > 1:
756 match += ',dl_vlan=%s' % vlan
757 action = {}
758 action['type'] = 'POP_VLAN'
759 flow['actions'].append(action)
760
stevenvanrossem27b6d952016-05-10 16:37:57 +0200761 else: # middle nodes
stevenvanrossem27b6d952016-05-10 16:37:57 +0200762 match += ',dl_vlan=%s' % vlan
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100763
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200764 # output action must come last
765 action = {}
766 action['type'] = 'OUTPUT'
767 action['port'] = switch_outport_nr
768 flow['actions'].append(action)
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200769
stevenvanrossem27b6d952016-05-10 16:37:57 +0200770 elif cmd == 'del-flows':
stevenvanrossem27b6d952016-05-10 16:37:57 +0200771 prefix = 'stats/flowentry/delete'
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200772
stevenvanrossem27b6d952016-05-10 16:37:57 +0200773 if cookie:
stevenvanrossem1ef77022016-05-12 16:36:10 +0200774 # TODO: add cookie_mask as argument
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200775 flow['cookie_mask'] = int('0xffffffffffffffff', 16) # need full mask to match complete cookie
stevenvanrossem27b6d952016-05-10 16:37:57 +0200776
777 action = {}
778 action['type'] = 'OUTPUT'
779 action['port'] = switch_outport_nr
780 flow['actions'].append(action)
781
782 flow['match'] = self._parse_match(match)
783 self.ryu_REST(prefix, data=flow)
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100784
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100785 def _set_vlan_tag(self, node, switch_port, tag):
786 node.vsctl('set', 'port {0} tag={1}'.format(switch_port,tag))
787 LOG.debug("set vlan in switch: {0} in_port: {1} vlan tag: {2}".format(node.name, switch_port, tag))
788
stevenvanrossem461941c2016-05-10 11:41:29 +0200789 def _set_flow_entry_dpctl(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100790
stevenvanrossem23c48092016-05-06 17:21:12 +0200791 match = 'in_port=%s' % switch_inport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200792
793 cookie = kwargs.get('cookie')
794 match_input = kwargs.get('match')
795 cmd = kwargs.get('cmd')
796 path = kwargs.get('path')
jokac304ad32017-01-09 10:58:23 +0100797 index = kwargs.get('pathindex')
stevenvanrossem461941c2016-05-10 11:41:29 +0200798 vlan = kwargs.get('vlan')
799
stevenvanrossem898a2af2016-05-06 18:28:57 +0200800 s = ','
801 if cookie:
802 cookie = 'cookie=%s' % cookie
803 match = s.join([cookie, match])
stevenvanrossem23c48092016-05-06 17:21:12 +0200804 if match_input:
stevenvanrossem23c48092016-05-06 17:21:12 +0200805 match = s.join([match, match_input])
stevenvanrossem23c48092016-05-06 17:21:12 +0200806 if cmd == 'add-flow':
807 action = 'action=%s' % switch_outport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200808 if vlan != None:
jokac304ad32017-01-09 10:58:23 +0100809 if index == 0: # first node
stevenvanrossem461941c2016-05-10 11:41:29 +0200810 action = ('action=mod_vlan_vid:%s' % vlan) + (',output=%s' % switch_outport_nr)
811 match = '-O OpenFlow13 ' + match
jokac304ad32017-01-09 10:58:23 +0100812 elif index == len(path) - 1: # last node
stevenvanrossem461941c2016-05-10 11:41:29 +0200813 match += ',dl_vlan=%s' % vlan
814 action = 'action=strip_vlan,output=%s' % switch_outport_nr
815 else: # middle nodes
816 match += ',dl_vlan=%s' % vlan
stevenvanrossem23c48092016-05-06 17:21:12 +0200817 ofcmd = s.join([match, action])
818 elif cmd == 'del-flows':
819 ofcmd = match
820 else:
821 ofcmd = ''
822
823 node.dpctl(cmd, ofcmd)
peustermf9a817d2016-07-18 09:06:04 +0200824 LOG.info("{3} in switch: {0} in_port: {1} out_port: {2}".format(node.name, switch_inport_nr,
stevenvanrossem461941c2016-05-10 11:41:29 +0200825 switch_outport_nr, cmd))
stevenvanrossem23c48092016-05-06 17:21:12 +0200826
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100827 # start Ryu Openflow controller as Remote Controller for the DCNetwork
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200828 def startRyu(self, learning_switch=True):
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100829 # start Ryu controller with rest-API
830 python_install_path = site.getsitepackages()[0]
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100831 # ryu default learning switch
832 #ryu_path = python_install_path + '/ryu/app/simple_switch_13.py'
833 #custom learning switch that installs a default NORMAL action in the ovs switches
834 dir_path = os.path.dirname(os.path.realpath(__file__))
835 ryu_path = dir_path + '/son_emu_simple_switch_13.py'
peustermde14f332016-03-15 16:14:21 +0100836 ryu_path2 = python_install_path + '/ryu/app/ofctl_rest.py'
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100837 # change the default Openflow controller port to 6653 (official IANA-assigned port number), as used by Mininet
838 # Ryu still uses 6633 as default
839 ryu_option = '--ofp-tcp-listen-port'
840 ryu_of_port = '6653'
peustermde14f332016-03-15 16:14:21 +0100841 ryu_cmd = 'ryu-manager'
peustermef6629e2016-03-14 17:21:56 +0100842 FNULL = open("/tmp/ryu.log", 'w')
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200843 if learning_switch:
844 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 +0100845 LOG.debug('starting ryu-controller with {0}'.format(ryu_path))
846 LOG.debug('starting ryu-controller with {0}'.format(ryu_path2))
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200847 else:
stevenvanrossem73efd192016-06-29 01:44:07 +0200848 # no learning switch, but with rest api
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200849 self.ryu_process = Popen([ryu_cmd, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100850 LOG.debug('starting ryu-controller with {0}'.format(ryu_path2))
peusterm391773a2016-03-14 17:40:43 +0100851 time.sleep(1)
852
peusterm8b04b532016-07-19 16:55:38 +0200853 def killRyu(self):
854 """
855 Stop the Ryu controller that might be started by son-emu.
856 :return:
857 """
858 # try it nicely
peustermde14f332016-03-15 16:14:21 +0100859 if self.ryu_process is not None:
peusterm391773a2016-03-14 17:40:43 +0100860 self.ryu_process.terminate()
861 self.ryu_process.kill()
peusterm8b04b532016-07-19 16:55:38 +0200862 # ensure its death ;-)
stevenvanrossem89706802016-07-19 02:54:45 +0200863 Popen(['pkill', '-f', 'ryu-manager'])
peusterm391773a2016-03-14 17:40:43 +0100864
stevenvanrossem27b6d952016-05-10 16:37:57 +0200865 def ryu_REST(self, prefix, dpid=None, data=None):
stevenvanrossem27b6d952016-05-10 16:37:57 +0200866
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200867 if dpid:
868 url = self.ryu_REST_api + '/' + str(prefix) + '/' + str(dpid)
869 else:
870 url = self.ryu_REST_api + '/' + str(prefix)
871 if data:
872 req = self.RyuSession.post(url, json=data)
873 else:
874 req = self.RyuSession.get(url)
875
876
877 # do extra logging if status code is not 200 (OK)
878 if req.status_code is not requests.codes.ok:
879 logging.info(
880 'type {0} encoding: {1} text: {2} headers: {3} history: {4}'.format(req.headers['content-type'],
881 req.encoding, req.text,
882 req.headers, req.history))
883 LOG.info('url: {0}'.format(str(url)))
884 if data: LOG.info('POST: {0}'.format(str(data)))
885 LOG.info('status: {0} reason: {1}'.format(req.status_code, req.reason))
886
887
888 if 'json' in req.headers['content-type']:
889 ret = req.json()
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200890 return ret
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200891
892 ret = req.text.rstrip()
893 return ret
894
stevenvanrossem27b6d952016-05-10 16:37:57 +0200895
896 # need to respect that some match fields must be integers
897 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#description-of-match-and-actions
898 def _parse_match(self, match):
899 matches = match.split(',')
900 dict = {}
901 for m in matches:
902 match = m.split('=')
903 if len(match) == 2:
904 try:
905 m2 = int(match[1], 0)
906 except:
907 m2 = match[1]
908
909 dict.update({match[0]:m2})
910 return dict
911
stevenvanrossem566779d2016-11-07 06:33:44 +0100912 def find_connected_dc_interface(self, vnf_src_name, vnf_src_interface):
913 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
914 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
915 for link in link_dict:
916 if (link_dict[link]['src_port_id'] == vnf_src_interface or
917 link_dict[link]['src_port_name'] == vnf_src_interface): # Fix: we might also get interface names, e.g, from a son-emu-cli call
918 # found the right link and connected switch
919 src_sw = connected_sw
920 src_sw_inport_nr = link_dict[link]['dst_port_nr']
921 src_sw_inport_name = link_dict[link]['dst_port_name']
stevenvanrossem7062cee2016-12-22 10:31:38 +0100922 return src_sw_inport_name