blob: af6fbadb16f8ef007130d9e777cd6d78f61db14a [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
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):
stevenvanrossembf1754e2016-11-17 10:20:52 +0100345 """
346 Add a monitoring flow entry that adds a special flowentry/counter at the begin or end of a chain.
347 So this monitoring flowrule exists on top of a previously defined chain rule and uses the same vlan tag/routing.
348 :param vnf_src_name:
349 :param vnf_dst_name:
350 :param vnf_src_interface:
351 :param vnf_dst_interface:
352 :param tag: vlan tag to be used for this chain (same tag as existing chain)
353 :param monitor_placement: 'tx' or 'rx' indicating to place the extra flowentry resp. at the beginning or end of the chain
354 :return:
355 """
stevenvanrossembecc7c52016-11-07 05:52:01 +0100356
357 src_sw = None
358 src_sw_inport_nr = 0
359 src_sw_inport_name = None
360 dst_sw = None
361 dst_sw_outport_nr = 0
362 dst_sw_outport_name = None
363
364 LOG.debug("call AddMonitorFlow vnf_src_name=%r, vnf_src_interface=%r, vnf_dst_name=%r, vnf_dst_interface=%r",
365 vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface)
366
367 #check if port is specified (vnf:port)
368 if vnf_src_interface is None:
369 # take first interface by default
370 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
371 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
372 vnf_src_interface = link_dict[0]['src_port_id']
373
374 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
375 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
376 for link in link_dict:
377 if (link_dict[link]['src_port_id'] == vnf_src_interface or
378 link_dict[link]['src_port_name'] == vnf_src_interface): # Fix: we might also get interface names, e.g, from a son-emu-cli call
379 # found the right link and connected switch
380 src_sw = connected_sw
381 src_sw_inport_nr = link_dict[link]['dst_port_nr']
382 src_sw_inport_name = link_dict[link]['dst_port_name']
383 break
384
385 if vnf_dst_interface is None:
386 # take first interface by default
387 connected_sw = self.DCNetwork_graph.neighbors(vnf_dst_name)[0]
388 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
389 vnf_dst_interface = link_dict[0]['dst_port_id']
390
391 vnf_dst_name = vnf_dst_name.split(':')[0]
392 for connected_sw in self.DCNetwork_graph.neighbors(vnf_dst_name):
393 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
394 for link in link_dict:
395 if link_dict[link]['dst_port_id'] == vnf_dst_interface or \
396 link_dict[link]['dst_port_name'] == vnf_dst_interface: # Fix: we might also get interface names, e.g, from a son-emu-cli call
397 # found the right link and connected switch
398 dst_sw = connected_sw
399 dst_sw_outport_nr = link_dict[link]['src_port_nr']
400 dst_sw_outport_name = link_dict[link]['src_port_name']
401 break
402
403 if not tag >= 0:
404 LOG.exception('tag not valid: {0}'.format(tag))
405
406 # get shortest path
407 try:
408 # returns the first found shortest path
409 # if all shortest paths are wanted, use: all_shortest_paths
410 path = nx.shortest_path(self.DCNetwork_graph, src_sw, dst_sw, weight=kwargs.get('weight'))
411 except:
412 LOG.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
413 vnf_src_name, vnf_dst_name, src_sw, dst_sw))
414 LOG.debug("Graph nodes: %r" % self.DCNetwork_graph.nodes())
415 LOG.debug("Graph edges: %r" % self.DCNetwork_graph.edges())
416 for e, v in self.DCNetwork_graph.edges():
417 LOG.debug("%r" % self.DCNetwork_graph[e][v])
418 return "No path could be found between {0} and {1}".format(vnf_src_name, vnf_dst_name)
419
420 LOG.info("Path between {0} and {1}: {2}".format(vnf_src_name, vnf_dst_name, path))
421
422 current_hop = src_sw
423 switch_inport_nr = src_sw_inport_nr
424
425 cmd = kwargs.get('cmd')
426
427 #iterate through the path to install the flow-entries
428 for i in range(0,len(path)):
429 current_node = self.getNodeByName(current_hop)
430
431 if path.index(current_hop) < len(path)-1:
432 next_hop = path[path.index(current_hop)+1]
433 else:
434 #last switch reached
435 next_hop = vnf_dst_name
436
437 next_node = self.getNodeByName(next_hop)
438
439 if next_hop == vnf_dst_name:
440 switch_outport_nr = dst_sw_outport_nr
441 LOG.info("end node reached: {0}".format(vnf_dst_name))
442 elif not isinstance( next_node, OVSSwitch ):
443 LOG.info("Next node: {0} is not a switch".format(next_hop))
444 return "Next node: {0} is not a switch".format(next_hop)
445 else:
446 # take first link between switches by default
447 index_edge_out = 0
448 switch_outport_nr = self.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100449
450
stevenvanrossembecc7c52016-11-07 05:52:01 +0100451 # set of entry via ovs-ofctl
452 if isinstance( current_node, OVSSwitch ):
453 kwargs['vlan'] = tag
454 kwargs['path'] = path
455 kwargs['current_hop'] = current_hop
456 kwargs['switch_inport_name'] = src_sw_inport_name
457 kwargs['switch_outport_name'] = dst_sw_outport_name
458 kwargs['skip_vlan_tag'] = True
stevenvanrossem263eee52017-02-08 01:04:36 +0100459 kwargs['pathindex'] = i
stevenvanrossembecc7c52016-11-07 05:52:01 +0100460
stevenvanrossem4fac2af2016-12-22 01:26:02 +0100461 monitor_placement = kwargs.get('monitor_placement').strip()
stevenvanrossembecc7c52016-11-07 05:52:01 +0100462 # put monitor flow at the dst switch
463 insert_flow = False
464 if monitor_placement == 'tx' and path.index(current_hop) == 0: # first node:
465 insert_flow = True
466 # put monitoring flow at the src switch
467 elif monitor_placement == 'rx' and path.index(current_hop) == len(path) - 1: # last node:
468 insert_flow = True
stevenvanrossem4fac2af2016-12-22 01:26:02 +0100469 elif monitor_placement not in ['rx', 'tx']:
stevenvanrossembecc7c52016-11-07 05:52:01 +0100470 LOG.exception('invalid monitor command: {0}'.format(monitor_placement))
471
472
473 if self.controller == RemoteController and insert_flow:
474 ## set flow entry via ryu rest api
475 self._set_flow_entry_ryu_rest(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
476 break
477 elif insert_flow:
478 ## set flow entry via ovs-ofctl
479 self._set_flow_entry_dpctl(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
480 break
481
482 # take first link between switches by default
483 if isinstance( next_node, OVSSwitch ):
484 switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
485 current_hop = next_hop
486
487 return "path {2} between {0} and {1}".format(vnf_src_name, vnf_dst_name, cmd)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100488
489
stevenvanrossem461941c2016-05-10 11:41:29 +0200490 def setChain(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200491 """
492 Chain 2 vnf interfaces together by installing the flowrules in the switches along their path.
493 Currently the path is found using the default networkx shortest path function.
494 Each chain gets a unique vlan id , so different chains wil not interfere.
495
496 :param vnf_src_name: vnf name (string)
497 :param vnf_dst_name: vnf name (string)
498 :param vnf_src_interface: source interface name (string)
499 :param vnf_dst_interface: destination interface name (string)
500 :param cmd: 'add-flow' (default) to add a chain, 'del-flows' to remove a chain
501 :param cookie: cookie for the installed flowrules (can be used later as identifier for a set of installed chains)
502 :param match: custom match entry to be added to the flowrules (default: only in_port and vlan tag)
503 :param priority: custom flowrule priority
stevenvanrossembf1754e2016-11-17 10:20:52 +0100504 :param monitor: boolean to indicate whether this chain is a monitoring chain
505 :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 +0100506 :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 +0100507 :param path: custom path between the two VNFs (list of switches)
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200508 :return: output log string
509 """
stevenvanrossembecc7c52016-11-07 05:52:01 +0100510
511 # special procedure for monitoring flows
512 if kwargs.get('monitor'):
513
514 # check if chain already exists
515 found_chains = [chain_dict for chain_dict in self.installed_chains if
516 (chain_dict['vnf_src_name'] == vnf_src_name and chain_dict['vnf_src_interface'] == vnf_src_interface
517 and chain_dict['vnf_dst_name'] == vnf_dst_name and chain_dict['vnf_dst_interface'] == vnf_dst_interface)]
518
519 if len(found_chains) > 0:
520 # this chain exists, so need an extra monitoring flow
521 # assume only 1 chain per vnf/interface pair
522 LOG.debug('*** installing monitoring chain on top of pre-defined chain from {0}:{1} -> {2}:{3}'.
523 format(vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface))
524 tag = found_chains[0]['tag']
525 ret = self._addMonitorFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface,
526 tag=tag, table_id=0, **kwargs)
527 return ret
528 else:
529 # no chain existing (or E-LAN) -> install normal chain
stevenvanrossem4fac2af2016-12-22 01:26:02 +0100530 LOG.warning('*** installing monitoring chain without pre-defined NSD chain from {0}:{1} -> {2}:{3}'.
stevenvanrossembecc7c52016-11-07 05:52:01 +0100531 format(vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface))
532 pass
533
534
stevenvanrossem461941c2016-05-10 11:41:29 +0200535 cmd = kwargs.get('cmd')
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100536 if cmd == 'add-flow' or cmd == 'del-flows':
stevenvanrossem461941c2016-05-10 11:41:29 +0200537 ret = self._chainAddFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface, **kwargs)
538 if kwargs.get('bidirectional'):
jokac304ad32017-01-09 10:58:23 +0100539 if kwargs.get('path') is not None:
540 kwargs['path'] = list(reversed(kwargs.get('path')))
stevenvanrossem81955a52016-05-12 14:34:12 +0200541 ret = ret +'\n' + self._chainAddFlow(vnf_dst_name, vnf_src_name, vnf_dst_interface, vnf_src_interface, **kwargs)
stevenvanrossem9315da42016-04-11 12:10:06 +0200542
stevenvanrossem461941c2016-05-10 11:41:29 +0200543 else:
stevenvanrossem81955a52016-05-12 14:34:12 +0200544 ret = "Command unknown"
545
546 return ret
stevenvanrossem461941c2016-05-10 11:41:29 +0200547
548
549 def _chainAddFlow(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
550
peusterm53d3c142016-07-18 10:10:11 +0200551 src_sw = None
peusterm53d3c142016-07-18 10:10:11 +0200552 src_sw_inport_nr = 0
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100553 src_sw_inport_name = None
554 dst_sw = None
peusterm53d3c142016-07-18 10:10:11 +0200555 dst_sw_outport_nr = 0
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100556 dst_sw_outport_name = None
peusterm53d3c142016-07-18 10:10:11 +0200557
558 LOG.debug("call chainAddFlow vnf_src_name=%r, vnf_src_interface=%r, vnf_dst_name=%r, vnf_dst_interface=%r",
559 vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface)
560
stevenvanrossem9315da42016-04-11 12:10:06 +0200561 #check if port is specified (vnf:port)
stevenvanrossemed711fd2016-04-11 16:59:29 +0200562 if vnf_src_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200563 # take first interface by default
564 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
565 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200566 vnf_src_interface = link_dict[0]['src_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200567
stevenvanrossem9315da42016-04-11 12:10:06 +0200568 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
569 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
570 for link in link_dict:
peusterm53d3c142016-07-18 10:10:11 +0200571 if (link_dict[link]['src_port_id'] == vnf_src_interface or
572 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 +0200573 # found the right link and connected switch
stevenvanrossem9315da42016-04-11 12:10:06 +0200574 src_sw = connected_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200575 src_sw_inport_nr = link_dict[link]['dst_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100576 src_sw_inport_name = link_dict[link]['dst_port_name']
stevenvanrossem9315da42016-04-11 12:10:06 +0200577 break
578
stevenvanrossemed711fd2016-04-11 16:59:29 +0200579 if vnf_dst_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200580 # take first interface by default
581 connected_sw = self.DCNetwork_graph.neighbors(vnf_dst_name)[0]
582 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200583 vnf_dst_interface = link_dict[0]['dst_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200584
585 vnf_dst_name = vnf_dst_name.split(':')[0]
586 for connected_sw in self.DCNetwork_graph.neighbors(vnf_dst_name):
587 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
588 for link in link_dict:
peusterm53d3c142016-07-18 10:10:11 +0200589 if link_dict[link]['dst_port_id'] == vnf_dst_interface or \
590 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 +0200591 # found the right link and connected switch
592 dst_sw = connected_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200593 dst_sw_outport_nr = link_dict[link]['src_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100594 dst_sw_outport_name = link_dict[link]['src_port_name']
stevenvanrossem9315da42016-04-11 12:10:06 +0200595 break
596
jokac304ad32017-01-09 10:58:23 +0100597 path = kwargs.get('path')
598 if path is None:
599 # get shortest path
600 try:
601 # returns the first found shortest path
602 # if all shortest paths are wanted, use: all_shortest_paths
603 path = nx.shortest_path(self.DCNetwork_graph, src_sw, dst_sw, weight=kwargs.get('weight'))
604 except:
605 LOG.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
606 vnf_src_name, vnf_dst_name, src_sw, dst_sw))
607 LOG.debug("Graph nodes: %r" % self.DCNetwork_graph.nodes())
608 LOG.debug("Graph edges: %r" % self.DCNetwork_graph.edges())
609 for e, v in self.DCNetwork_graph.edges():
610 LOG.debug("%r" % self.DCNetwork_graph[e][v])
611 return "No path could be found between {0} and {1}".format(vnf_src_name, vnf_dst_name)
stevenvanrossem9315da42016-04-11 12:10:06 +0200612
peustermf9a817d2016-07-18 09:06:04 +0200613 LOG.info("Path between {0} and {1}: {2}".format(vnf_src_name, vnf_dst_name, path))
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100614
stevenvanrossem9315da42016-04-11 12:10:06 +0200615 current_hop = src_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200616 switch_inport_nr = src_sw_inport_nr
stevenvanrossem9315da42016-04-11 12:10:06 +0200617
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100618 # choose free vlan
stevenvanrossem27b6d952016-05-10 16:37:57 +0200619 cmd = kwargs.get('cmd')
620 vlan = None
stevenvanrossem263eee52017-02-08 01:04:36 +0100621 if cmd == 'add-flow':
stevenvanrossembecc7c52016-11-07 05:52:01 +0100622 if kwargs.get('tag'):
623 # use pre-defined tag
624 vlan = kwargs.get('tag')
625 else:
626 vlan = self.vlans.pop()
stevenvanrossem461941c2016-05-10 11:41:29 +0200627
stevenvanrossembecc7c52016-11-07 05:52:01 +0100628 # store the used vlan tag to identify this chain
629 if not kwargs.get('monitor'):
630 chain_dict = {}
631 chain_dict['vnf_src_name'] = vnf_src_name
632 chain_dict['vnf_dst_name'] = vnf_dst_name
633 chain_dict['vnf_src_interface'] = vnf_src_interface
634 chain_dict['vnf_dst_interface'] = vnf_dst_interface
635 chain_dict['tag'] = vlan
636 self.installed_chains.append(chain_dict)
637
638 #iterate through the path to install the flow-entries
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100639 for i in range(0,len(path)):
stevenvanrossem9315da42016-04-11 12:10:06 +0200640 current_node = self.getNodeByName(current_hop)
stevenvanrossem461941c2016-05-10 11:41:29 +0200641
jokac304ad32017-01-09 10:58:23 +0100642 if i < len(path) - 1:
643 next_hop = path[i + 1]
stevenvanrossem9315da42016-04-11 12:10:06 +0200644 else:
jokac304ad32017-01-09 10:58:23 +0100645 # last switch reached
stevenvanrossem9315da42016-04-11 12:10:06 +0200646 next_hop = vnf_dst_name
647
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100648 next_node = self.getNodeByName(next_hop)
649
650 if next_hop == vnf_dst_name:
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200651 switch_outport_nr = dst_sw_outport_nr
peustermf9a817d2016-07-18 09:06:04 +0200652 LOG.info("end node reached: {0}".format(vnf_dst_name))
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100653 elif not isinstance( next_node, OVSSwitch ):
peustermf9a817d2016-07-18 09:06:04 +0200654 LOG.info("Next node: {0} is not a switch".format(next_hop))
stevenvanrossemeefea6c2016-02-17 12:03:26 +0100655 return "Next node: {0} is not a switch".format(next_hop)
stevenvanrossem9315da42016-04-11 12:10:06 +0200656 else:
stevenvanrossemed711fd2016-04-11 16:59:29 +0200657 # take first link between switches by default
stevenvanrossem9315da42016-04-11 12:10:06 +0200658 index_edge_out = 0
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200659 switch_outport_nr = self.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
stevenvanrossem9315da42016-04-11 12:10:06 +0200660
stevenvanrossem9315da42016-04-11 12:10:06 +0200661
stevenvanrossem634c5ef2017-02-08 00:46:02 +0100662 # set OpenFlow entry
stevenvanrossem9315da42016-04-11 12:10:06 +0200663 if isinstance( current_node, OVSSwitch ):
stevenvanrossem461941c2016-05-10 11:41:29 +0200664 kwargs['vlan'] = vlan
665 kwargs['path'] = path
666 kwargs['current_hop'] = current_hop
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100667 kwargs['switch_inport_name'] = src_sw_inport_name
668 kwargs['switch_outport_name'] = dst_sw_outport_name
jokac304ad32017-01-09 10:58:23 +0100669 kwargs['pathindex'] = i
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200670
671 if self.controller == RemoteController:
672 ## set flow entry via ryu rest api
673 self._set_flow_entry_ryu_rest(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
674 else:
675 ## set flow entry via ovs-ofctl
676 self._set_flow_entry_dpctl(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
677
stevenvanrossemed711fd2016-04-11 16:59:29 +0200678 # take first link between switches by default
679 if isinstance( next_node, OVSSwitch ):
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200680 switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
stevenvanrossemed711fd2016-04-11 16:59:29 +0200681 current_hop = next_hop
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100682
stevenvanrossem27b6d952016-05-10 16:37:57 +0200683 return "path {2} between {0} and {1}".format(vnf_src_name, vnf_dst_name, cmd)
684
685 def _set_flow_entry_ryu_rest(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
686 match = 'in_port=%s' % switch_inport_nr
687
688 cookie = kwargs.get('cookie')
689 match_input = kwargs.get('match')
690 cmd = kwargs.get('cmd')
691 path = kwargs.get('path')
jokac304ad32017-01-09 10:58:23 +0100692 index = kwargs.get('pathindex')
693
stevenvanrossem27b6d952016-05-10 16:37:57 +0200694 vlan = kwargs.get('vlan')
stevenvanrossem61699eb2016-08-05 15:57:59 +0200695 priority = kwargs.get('priority')
stevenvanrossembecc7c52016-11-07 05:52:01 +0100696 # flag to not set the ovs port vlan tag
697 skip_vlan_tag = kwargs.get('skip_vlan_tag')
698 # table id to put this flowentry
699 table_id = kwargs.get('table_id')
700 if not table_id:
701 table_id = 0
stevenvanrossem27b6d952016-05-10 16:37:57 +0200702
703 s = ','
704 if match_input:
705 match = s.join([match, match_input])
706
707 flow = {}
708 flow['dpid'] = int(node.dpid, 16)
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200709
stevenvanrossem27b6d952016-05-10 16:37:57 +0200710 if cookie:
711 flow['cookie'] = int(cookie)
stevenvanrossem61699eb2016-08-05 15:57:59 +0200712 if priority:
713 flow['priority'] = int(priority)
stevenvanrossem27b6d952016-05-10 16:37:57 +0200714
stevenvanrossembecc7c52016-11-07 05:52:01 +0100715 flow['table_id'] = table_id
716
stevenvanrossem27b6d952016-05-10 16:37:57 +0200717 flow['actions'] = []
718
719 # possible Ryu actions, match fields:
720 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#add-a-flow-entry
721 if cmd == 'add-flow':
722 prefix = 'stats/flowentry/add'
stevenvanrossem27b6d952016-05-10 16:37:57 +0200723 if vlan != None:
jokac304ad32017-01-09 10:58:23 +0100724 if index == 0: # first node
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100725 # set vlan tag in ovs instance (to isolate E-LANs)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100726 if not skip_vlan_tag:
727 in_port_name = kwargs.get('switch_inport_name')
728 self._set_vlan_tag(node, in_port_name, vlan)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100729 # set vlan push action if more than 1 switch in the path
730 if len(path) > 1:
731 action = {}
732 action['type'] = 'PUSH_VLAN' # Push a new VLAN tag if a input frame is non-VLAN-tagged
733 action['ethertype'] = 33024 # Ethertype 0x8100(=33024): IEEE 802.1Q VLAN-tagged frame
734 flow['actions'].append(action)
735 action = {}
736 action['type'] = 'SET_FIELD'
737 action['field'] = 'vlan_vid'
stevenvanrossem9cc73602017-01-27 23:37:29 +0100738 # ryu expects the field to be masked
739 action['value'] = vlan | 0x1000
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100740 flow['actions'].append(action)
741
stevenvanrossem9cc73602017-01-27 23:37:29 +0100742 elif index == len(path) - 1: # last node
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100743 # set vlan tag in ovs instance (to isolate E-LANs)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100744 if not skip_vlan_tag:
745 out_port_name = kwargs.get('switch_outport_name')
746 self._set_vlan_tag(node, out_port_name, vlan)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100747 # set vlan pop action if more than 1 switch in the path
748 if len(path) > 1:
749 match += ',dl_vlan=%s' % vlan
750 action = {}
751 action['type'] = 'POP_VLAN'
752 flow['actions'].append(action)
753
stevenvanrossem27b6d952016-05-10 16:37:57 +0200754 else: # middle nodes
stevenvanrossem27b6d952016-05-10 16:37:57 +0200755 match += ',dl_vlan=%s' % vlan
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100756
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200757 # output action must come last
758 action = {}
759 action['type'] = 'OUTPUT'
760 action['port'] = switch_outport_nr
761 flow['actions'].append(action)
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200762
stevenvanrossem27b6d952016-05-10 16:37:57 +0200763 elif cmd == 'del-flows':
stevenvanrossem27b6d952016-05-10 16:37:57 +0200764 prefix = 'stats/flowentry/delete'
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200765
stevenvanrossem27b6d952016-05-10 16:37:57 +0200766 if cookie:
stevenvanrossem1ef77022016-05-12 16:36:10 +0200767 # TODO: add cookie_mask as argument
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200768 flow['cookie_mask'] = int('0xffffffffffffffff', 16) # need full mask to match complete cookie
stevenvanrossem27b6d952016-05-10 16:37:57 +0200769
770 action = {}
771 action['type'] = 'OUTPUT'
772 action['port'] = switch_outport_nr
773 flow['actions'].append(action)
774
775 flow['match'] = self._parse_match(match)
776 self.ryu_REST(prefix, data=flow)
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100777
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100778 def _set_vlan_tag(self, node, switch_port, tag):
779 node.vsctl('set', 'port {0} tag={1}'.format(switch_port,tag))
780 LOG.debug("set vlan in switch: {0} in_port: {1} vlan tag: {2}".format(node.name, switch_port, tag))
781
stevenvanrossem461941c2016-05-10 11:41:29 +0200782 def _set_flow_entry_dpctl(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100783
stevenvanrossem23c48092016-05-06 17:21:12 +0200784 match = 'in_port=%s' % switch_inport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200785
786 cookie = kwargs.get('cookie')
787 match_input = kwargs.get('match')
788 cmd = kwargs.get('cmd')
789 path = kwargs.get('path')
jokac304ad32017-01-09 10:58:23 +0100790 index = kwargs.get('pathindex')
stevenvanrossem461941c2016-05-10 11:41:29 +0200791 vlan = kwargs.get('vlan')
792
stevenvanrossem898a2af2016-05-06 18:28:57 +0200793 s = ','
794 if cookie:
795 cookie = 'cookie=%s' % cookie
796 match = s.join([cookie, match])
stevenvanrossem23c48092016-05-06 17:21:12 +0200797 if match_input:
stevenvanrossem23c48092016-05-06 17:21:12 +0200798 match = s.join([match, match_input])
stevenvanrossem23c48092016-05-06 17:21:12 +0200799 if cmd == 'add-flow':
800 action = 'action=%s' % switch_outport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200801 if vlan != None:
jokac304ad32017-01-09 10:58:23 +0100802 if index == 0: # first node
stevenvanrossem461941c2016-05-10 11:41:29 +0200803 action = ('action=mod_vlan_vid:%s' % vlan) + (',output=%s' % switch_outport_nr)
804 match = '-O OpenFlow13 ' + match
jokac304ad32017-01-09 10:58:23 +0100805 elif index == len(path) - 1: # last node
stevenvanrossem461941c2016-05-10 11:41:29 +0200806 match += ',dl_vlan=%s' % vlan
807 action = 'action=strip_vlan,output=%s' % switch_outport_nr
808 else: # middle nodes
809 match += ',dl_vlan=%s' % vlan
stevenvanrossem23c48092016-05-06 17:21:12 +0200810 ofcmd = s.join([match, action])
811 elif cmd == 'del-flows':
812 ofcmd = match
813 else:
814 ofcmd = ''
815
816 node.dpctl(cmd, ofcmd)
peustermf9a817d2016-07-18 09:06:04 +0200817 LOG.info("{3} in switch: {0} in_port: {1} out_port: {2}".format(node.name, switch_inport_nr,
stevenvanrossem461941c2016-05-10 11:41:29 +0200818 switch_outport_nr, cmd))
stevenvanrossem23c48092016-05-06 17:21:12 +0200819
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100820 # start Ryu Openflow controller as Remote Controller for the DCNetwork
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200821 def startRyu(self, learning_switch=True):
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100822 # start Ryu controller with rest-API
823 python_install_path = site.getsitepackages()[0]
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100824 # ryu default learning switch
825 #ryu_path = python_install_path + '/ryu/app/simple_switch_13.py'
826 #custom learning switch that installs a default NORMAL action in the ovs switches
827 dir_path = os.path.dirname(os.path.realpath(__file__))
828 ryu_path = dir_path + '/son_emu_simple_switch_13.py'
peustermde14f332016-03-15 16:14:21 +0100829 ryu_path2 = python_install_path + '/ryu/app/ofctl_rest.py'
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100830 # change the default Openflow controller port to 6653 (official IANA-assigned port number), as used by Mininet
831 # Ryu still uses 6633 as default
832 ryu_option = '--ofp-tcp-listen-port'
833 ryu_of_port = '6653'
peustermde14f332016-03-15 16:14:21 +0100834 ryu_cmd = 'ryu-manager'
peustermef6629e2016-03-14 17:21:56 +0100835 FNULL = open("/tmp/ryu.log", 'w')
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200836 if learning_switch:
837 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 +0100838 LOG.debug('starting ryu-controller with {0}'.format(ryu_path))
839 LOG.debug('starting ryu-controller with {0}'.format(ryu_path2))
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200840 else:
stevenvanrossem73efd192016-06-29 01:44:07 +0200841 # no learning switch, but with rest api
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200842 self.ryu_process = Popen([ryu_cmd, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100843 LOG.debug('starting ryu-controller with {0}'.format(ryu_path2))
peusterm391773a2016-03-14 17:40:43 +0100844 time.sleep(1)
845
peusterm8b04b532016-07-19 16:55:38 +0200846 def killRyu(self):
847 """
848 Stop the Ryu controller that might be started by son-emu.
849 :return:
850 """
851 # try it nicely
peustermde14f332016-03-15 16:14:21 +0100852 if self.ryu_process is not None:
peusterm391773a2016-03-14 17:40:43 +0100853 self.ryu_process.terminate()
854 self.ryu_process.kill()
peusterm8b04b532016-07-19 16:55:38 +0200855 # ensure its death ;-)
stevenvanrossem89706802016-07-19 02:54:45 +0200856 Popen(['pkill', '-f', 'ryu-manager'])
peusterm391773a2016-03-14 17:40:43 +0100857
stevenvanrossem27b6d952016-05-10 16:37:57 +0200858 def ryu_REST(self, prefix, dpid=None, data=None):
stevenvanrossem27b6d952016-05-10 16:37:57 +0200859
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200860 if dpid:
861 url = self.ryu_REST_api + '/' + str(prefix) + '/' + str(dpid)
862 else:
863 url = self.ryu_REST_api + '/' + str(prefix)
864 if data:
865 req = self.RyuSession.post(url, json=data)
866 else:
867 req = self.RyuSession.get(url)
868
869
870 # do extra logging if status code is not 200 (OK)
871 if req.status_code is not requests.codes.ok:
872 logging.info(
873 'type {0} encoding: {1} text: {2} headers: {3} history: {4}'.format(req.headers['content-type'],
874 req.encoding, req.text,
875 req.headers, req.history))
876 LOG.info('url: {0}'.format(str(url)))
877 if data: LOG.info('POST: {0}'.format(str(data)))
878 LOG.info('status: {0} reason: {1}'.format(req.status_code, req.reason))
879
880
881 if 'json' in req.headers['content-type']:
882 ret = req.json()
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200883 return ret
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200884
885 ret = req.text.rstrip()
886 return ret
887
stevenvanrossem27b6d952016-05-10 16:37:57 +0200888
889 # need to respect that some match fields must be integers
890 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#description-of-match-and-actions
891 def _parse_match(self, match):
892 matches = match.split(',')
893 dict = {}
894 for m in matches:
895 match = m.split('=')
896 if len(match) == 2:
897 try:
898 m2 = int(match[1], 0)
899 except:
900 m2 = match[1]
901
902 dict.update({match[0]:m2})
903 return dict
904
stevenvanrossem566779d2016-11-07 06:33:44 +0100905 def find_connected_dc_interface(self, vnf_src_name, vnf_src_interface):
906 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
907 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
908 for link in link_dict:
909 if (link_dict[link]['src_port_id'] == vnf_src_interface or
910 link_dict[link]['src_port_name'] == vnf_src_interface): # Fix: we might also get interface names, e.g, from a son-emu-cli call
911 # found the right link and connected switch
912 src_sw = connected_sw
913 src_sw_inport_nr = link_dict[link]['dst_port_nr']
914 src_sw_inport_name = link_dict[link]['dst_port_name']
stevenvanrossem7062cee2016-12-22 10:31:38 +0100915 return src_sw_inport_name