blob: faae0e348035729df0dd298c6ecda2ee07bb864b [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 """
stevenvanrossemf3712012017-05-04 00:01:52 +0200243 self.DCNetwork_graph.add_node(label, type=params.get('type', 'docker'))
peusterm5877ea22016-05-11 13:44:59 +0200244 return Containernet.addDocker(self, label, cls=EmulatorCompute, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100245
stevenvanrossemf3712012017-05-04 00:01:52 +0200246 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
stevenvanrossemf3712012017-05-04 00:01:52 +0200253 def addExtSAP(self, sap_name, sap_ip, **params):
254 """
255 Wrapper for addExtSAP method to store SAP also in graph.
256 """
257 # make sure that 'type' is set
258 params['type'] = params.get('type','sap_ext')
259 self.DCNetwork_graph.add_node(sap_name, type=params['type'])
260 LOG.info('add ext sap: {0}'.format(sap_name))
261 return Containernet.addExtSAP(self, sap_name, sap_ip, **params)
262
263 def removeExtSAP(self, sap_name, **params):
264 """
265 Wrapper for removeExtSAP method to remove SAP also from graph.
266 """
267 self.DCNetwork_graph.remove_node(sap_name)
268 return Containernet.removeExtSAP(self, sap_name)
269
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100270 def addSwitch( self, name, add_to_graph=True, **params ):
271 """
272 Wrapper for addSwitch method to store switch also in graph.
273 """
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100274
275 # add this switch to the global topology overview
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100276 if add_to_graph:
stevenvanrossemf3712012017-05-04 00:01:52 +0200277 self.DCNetwork_graph.add_node(name, type=params.get('type','switch'))
278 LOG.info('*** **** *** add switch: {0} type: {1}'.format(name, params.get('type')))
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100279
280 # set the learning switch behavior
281 if 'failMode' in params :
282 failMode = params['failMode']
283 else :
284 failMode = self.failMode
285
286 s = Containernet.addSwitch(self, name, protocols='OpenFlow10,OpenFlow12,OpenFlow13', failMode=failMode, **params)
287
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100288 return s
peustermc3b977e2016-01-12 10:09:35 +0100289
peustermbd44f4a2016-01-13 14:53:30 +0100290 def getAllContainers(self):
291 """
292 Returns a list with all containers within all data centers.
293 """
294 all_containers = []
295 for dc in self.dcs.itervalues():
296 all_containers += dc.listCompute()
297 return all_containers
298
peustermcbcd4c22015-12-28 11:33:42 +0100299 def start(self):
300 # start
301 for dc in self.dcs.itervalues():
302 dc.start()
peusterm5877ea22016-05-11 13:44:59 +0200303 Containernet.start(self)
peustermcbcd4c22015-12-28 11:33:42 +0100304
305 def stop(self):
stevenvanrossem60670da2016-04-15 15:31:28 +0200306
stevenvanrossemc6abf132016-04-14 11:15:58 +0200307 # stop the monitor agent
stevenvanrossem60670da2016-04-15 15:31:28 +0200308 if self.monitor_agent is not None:
309 self.monitor_agent.stop()
peustermcbcd4c22015-12-28 11:33:42 +0100310
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200311 # stop emulator net
peusterm5877ea22016-05-11 13:44:59 +0200312 Containernet.stop(self)
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200313
314 # stop Ryu controller
peusterm8b04b532016-07-19 16:55:38 +0200315 self.killRyu()
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200316
317
peustermcbcd4c22015-12-28 11:33:42 +0100318 def CLI(self):
peusterm293cbc32016-01-13 17:05:28 +0100319 CLI(self)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100320
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100321 def setLAN(self, vnf_list):
322 """
323 setup an E-LAN network by assigning the same VLAN tag to each DC interface of the VNFs in the E-LAN
324
325 :param vnf_list: names of the VNFs in this E-LAN [{name:,interface:},...]
326 :return:
327 """
328 src_sw = None
329 src_sw_inport_nr = 0
330 src_sw_inport_name = None
331
332 # get a vlan tag for this E-LAN
333 vlan = self.vlans.pop()
334
335 for vnf in vnf_list:
336 vnf_src_name = vnf['name']
337 vnf_src_interface = vnf['interface']
338
339 # check if port is specified (vnf:port)
340 if vnf_src_interface is None:
341 # take first interface by default
342 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
343 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
344 vnf_src_interface = link_dict[0]['src_port_id']
345
346 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
347 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
348 for link in link_dict:
349 if (link_dict[link]['src_port_id'] == vnf_src_interface or
350 link_dict[link]['src_port_name'] == vnf_src_interface): # Fix: we might also get interface names, e.g, from a son-emu-cli call
351 # found the right link and connected switch
352 src_sw = connected_sw
353 src_sw_inport_nr = link_dict[link]['dst_port_nr']
354 src_sw_inport_name = link_dict[link]['dst_port_name']
355 break
356
357 # set the tag on the dc switch interface
358 LOG.debug('set E-LAN: vnf name: {0} interface: {1} tag: {2}'.format(vnf_src_name, vnf_src_interface,vlan))
359 switch_node = self.getNodeByName(src_sw)
360 self._set_vlan_tag(switch_node, src_sw_inport_name, vlan)
361
stevenvanrossembecc7c52016-11-07 05:52:01 +0100362 def _addMonitorFlow(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None,
363 tag=None, **kwargs):
stevenvanrossembf1754e2016-11-17 10:20:52 +0100364 """
365 Add a monitoring flow entry that adds a special flowentry/counter at the begin or end of a chain.
366 So this monitoring flowrule exists on top of a previously defined chain rule and uses the same vlan tag/routing.
367 :param vnf_src_name:
368 :param vnf_dst_name:
369 :param vnf_src_interface:
370 :param vnf_dst_interface:
371 :param tag: vlan tag to be used for this chain (same tag as existing chain)
372 :param monitor_placement: 'tx' or 'rx' indicating to place the extra flowentry resp. at the beginning or end of the chain
373 :return:
374 """
stevenvanrossembecc7c52016-11-07 05:52:01 +0100375
376 src_sw = None
377 src_sw_inport_nr = 0
378 src_sw_inport_name = None
379 dst_sw = None
380 dst_sw_outport_nr = 0
381 dst_sw_outport_name = None
382
383 LOG.debug("call AddMonitorFlow vnf_src_name=%r, vnf_src_interface=%r, vnf_dst_name=%r, vnf_dst_interface=%r",
384 vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface)
385
386 #check if port is specified (vnf:port)
387 if vnf_src_interface is None:
388 # take first interface by default
389 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
390 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
391 vnf_src_interface = link_dict[0]['src_port_id']
392
393 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
394 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
395 for link in link_dict:
396 if (link_dict[link]['src_port_id'] == vnf_src_interface or
397 link_dict[link]['src_port_name'] == vnf_src_interface): # Fix: we might also get interface names, e.g, from a son-emu-cli call
398 # found the right link and connected switch
399 src_sw = connected_sw
400 src_sw_inport_nr = link_dict[link]['dst_port_nr']
401 src_sw_inport_name = link_dict[link]['dst_port_name']
402 break
403
404 if vnf_dst_interface is None:
405 # take first interface by default
406 connected_sw = self.DCNetwork_graph.neighbors(vnf_dst_name)[0]
407 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
408 vnf_dst_interface = link_dict[0]['dst_port_id']
409
410 vnf_dst_name = vnf_dst_name.split(':')[0]
411 for connected_sw in self.DCNetwork_graph.neighbors(vnf_dst_name):
412 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
413 for link in link_dict:
414 if link_dict[link]['dst_port_id'] == vnf_dst_interface or \
415 link_dict[link]['dst_port_name'] == vnf_dst_interface: # Fix: we might also get interface names, e.g, from a son-emu-cli call
416 # found the right link and connected switch
417 dst_sw = connected_sw
418 dst_sw_outport_nr = link_dict[link]['src_port_nr']
419 dst_sw_outport_name = link_dict[link]['src_port_name']
420 break
421
422 if not tag >= 0:
423 LOG.exception('tag not valid: {0}'.format(tag))
424
425 # get shortest path
426 try:
427 # returns the first found shortest path
428 # if all shortest paths are wanted, use: all_shortest_paths
429 path = nx.shortest_path(self.DCNetwork_graph, src_sw, dst_sw, weight=kwargs.get('weight'))
430 except:
431 LOG.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
432 vnf_src_name, vnf_dst_name, src_sw, dst_sw))
433 LOG.debug("Graph nodes: %r" % self.DCNetwork_graph.nodes())
434 LOG.debug("Graph edges: %r" % self.DCNetwork_graph.edges())
435 for e, v in self.DCNetwork_graph.edges():
436 LOG.debug("%r" % self.DCNetwork_graph[e][v])
437 return "No path could be found between {0} and {1}".format(vnf_src_name, vnf_dst_name)
438
439 LOG.info("Path between {0} and {1}: {2}".format(vnf_src_name, vnf_dst_name, path))
440
441 current_hop = src_sw
442 switch_inport_nr = src_sw_inport_nr
443
444 cmd = kwargs.get('cmd')
445
446 #iterate through the path to install the flow-entries
447 for i in range(0,len(path)):
448 current_node = self.getNodeByName(current_hop)
449
450 if path.index(current_hop) < len(path)-1:
451 next_hop = path[path.index(current_hop)+1]
452 else:
453 #last switch reached
454 next_hop = vnf_dst_name
455
456 next_node = self.getNodeByName(next_hop)
457
458 if next_hop == vnf_dst_name:
459 switch_outport_nr = dst_sw_outport_nr
460 LOG.info("end node reached: {0}".format(vnf_dst_name))
461 elif not isinstance( next_node, OVSSwitch ):
462 LOG.info("Next node: {0} is not a switch".format(next_hop))
463 return "Next node: {0} is not a switch".format(next_hop)
464 else:
465 # take first link between switches by default
466 index_edge_out = 0
467 switch_outport_nr = self.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100468
469
stevenvanrossembecc7c52016-11-07 05:52:01 +0100470 # set of entry via ovs-ofctl
471 if isinstance( current_node, OVSSwitch ):
472 kwargs['vlan'] = tag
473 kwargs['path'] = path
474 kwargs['current_hop'] = current_hop
475 kwargs['switch_inport_name'] = src_sw_inport_name
476 kwargs['switch_outport_name'] = dst_sw_outport_name
477 kwargs['skip_vlan_tag'] = True
stevenvanrossem263eee52017-02-08 01:04:36 +0100478 kwargs['pathindex'] = i
stevenvanrossembecc7c52016-11-07 05:52:01 +0100479
stevenvanrossem4fac2af2016-12-22 01:26:02 +0100480 monitor_placement = kwargs.get('monitor_placement').strip()
stevenvanrossembecc7c52016-11-07 05:52:01 +0100481 # put monitor flow at the dst switch
482 insert_flow = False
483 if monitor_placement == 'tx' and path.index(current_hop) == 0: # first node:
484 insert_flow = True
485 # put monitoring flow at the src switch
486 elif monitor_placement == 'rx' and path.index(current_hop) == len(path) - 1: # last node:
487 insert_flow = True
stevenvanrossem4fac2af2016-12-22 01:26:02 +0100488 elif monitor_placement not in ['rx', 'tx']:
stevenvanrossembecc7c52016-11-07 05:52:01 +0100489 LOG.exception('invalid monitor command: {0}'.format(monitor_placement))
490
491
492 if self.controller == RemoteController and insert_flow:
493 ## set flow entry via ryu rest api
494 self._set_flow_entry_ryu_rest(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
495 break
496 elif insert_flow:
497 ## set flow entry via ovs-ofctl
498 self._set_flow_entry_dpctl(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
499 break
500
501 # take first link between switches by default
502 if isinstance( next_node, OVSSwitch ):
503 switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
504 current_hop = next_hop
505
506 return "path {2} between {0} and {1}".format(vnf_src_name, vnf_dst_name, cmd)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100507
508
stevenvanrossem461941c2016-05-10 11:41:29 +0200509 def setChain(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200510 """
511 Chain 2 vnf interfaces together by installing the flowrules in the switches along their path.
512 Currently the path is found using the default networkx shortest path function.
513 Each chain gets a unique vlan id , so different chains wil not interfere.
514
515 :param vnf_src_name: vnf name (string)
516 :param vnf_dst_name: vnf name (string)
517 :param vnf_src_interface: source interface name (string)
518 :param vnf_dst_interface: destination interface name (string)
519 :param cmd: 'add-flow' (default) to add a chain, 'del-flows' to remove a chain
520 :param cookie: cookie for the installed flowrules (can be used later as identifier for a set of installed chains)
521 :param match: custom match entry to be added to the flowrules (default: only in_port and vlan tag)
522 :param priority: custom flowrule priority
stevenvanrossembf1754e2016-11-17 10:20:52 +0100523 :param monitor: boolean to indicate whether this chain is a monitoring chain
524 :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 +0100525 :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 +0100526 :param path: custom path between the two VNFs (list of switches)
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200527 :return: output log string
528 """
stevenvanrossembecc7c52016-11-07 05:52:01 +0100529
530 # special procedure for monitoring flows
531 if kwargs.get('monitor'):
532
533 # check if chain already exists
534 found_chains = [chain_dict for chain_dict in self.installed_chains if
535 (chain_dict['vnf_src_name'] == vnf_src_name and chain_dict['vnf_src_interface'] == vnf_src_interface
536 and chain_dict['vnf_dst_name'] == vnf_dst_name and chain_dict['vnf_dst_interface'] == vnf_dst_interface)]
537
538 if len(found_chains) > 0:
539 # this chain exists, so need an extra monitoring flow
540 # assume only 1 chain per vnf/interface pair
541 LOG.debug('*** installing monitoring chain on top of pre-defined chain from {0}:{1} -> {2}:{3}'.
542 format(vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface))
543 tag = found_chains[0]['tag']
544 ret = self._addMonitorFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface,
545 tag=tag, table_id=0, **kwargs)
546 return ret
547 else:
548 # no chain existing (or E-LAN) -> install normal chain
stevenvanrossem4fac2af2016-12-22 01:26:02 +0100549 LOG.warning('*** installing monitoring chain without pre-defined NSD chain from {0}:{1} -> {2}:{3}'.
stevenvanrossembecc7c52016-11-07 05:52:01 +0100550 format(vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface))
551 pass
552
553
stevenvanrossem461941c2016-05-10 11:41:29 +0200554 cmd = kwargs.get('cmd')
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100555 if cmd == 'add-flow' or cmd == 'del-flows':
stevenvanrossem461941c2016-05-10 11:41:29 +0200556 ret = self._chainAddFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface, **kwargs)
557 if kwargs.get('bidirectional'):
jokac304ad32017-01-09 10:58:23 +0100558 if kwargs.get('path') is not None:
559 kwargs['path'] = list(reversed(kwargs.get('path')))
stevenvanrossem81955a52016-05-12 14:34:12 +0200560 ret = ret +'\n' + self._chainAddFlow(vnf_dst_name, vnf_src_name, vnf_dst_interface, vnf_src_interface, **kwargs)
stevenvanrossem9315da42016-04-11 12:10:06 +0200561
stevenvanrossem461941c2016-05-10 11:41:29 +0200562 else:
stevenvanrossem81955a52016-05-12 14:34:12 +0200563 ret = "Command unknown"
564
565 return ret
stevenvanrossem461941c2016-05-10 11:41:29 +0200566
567
568 def _chainAddFlow(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
569
peusterm53d3c142016-07-18 10:10:11 +0200570 src_sw = None
peusterm53d3c142016-07-18 10:10:11 +0200571 src_sw_inport_nr = 0
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100572 src_sw_inport_name = None
573 dst_sw = None
peusterm53d3c142016-07-18 10:10:11 +0200574 dst_sw_outport_nr = 0
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100575 dst_sw_outport_name = None
peusterm53d3c142016-07-18 10:10:11 +0200576
577 LOG.debug("call chainAddFlow vnf_src_name=%r, vnf_src_interface=%r, vnf_dst_name=%r, vnf_dst_interface=%r",
578 vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface)
579
stevenvanrossem9315da42016-04-11 12:10:06 +0200580 #check if port is specified (vnf:port)
stevenvanrossemed711fd2016-04-11 16:59:29 +0200581 if vnf_src_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200582 # take first interface by default
583 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
584 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200585 vnf_src_interface = link_dict[0]['src_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200586
stevenvanrossem9315da42016-04-11 12:10:06 +0200587 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
588 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
589 for link in link_dict:
peusterm53d3c142016-07-18 10:10:11 +0200590 if (link_dict[link]['src_port_id'] == vnf_src_interface or
591 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 +0200592 # found the right link and connected switch
stevenvanrossem9315da42016-04-11 12:10:06 +0200593 src_sw = connected_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200594 src_sw_inport_nr = link_dict[link]['dst_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100595 src_sw_inport_name = link_dict[link]['dst_port_name']
stevenvanrossem9315da42016-04-11 12:10:06 +0200596 break
597
stevenvanrossemed711fd2016-04-11 16:59:29 +0200598 if vnf_dst_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200599 # take first interface by default
600 connected_sw = self.DCNetwork_graph.neighbors(vnf_dst_name)[0]
601 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200602 vnf_dst_interface = link_dict[0]['dst_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200603
604 vnf_dst_name = vnf_dst_name.split(':')[0]
605 for connected_sw in self.DCNetwork_graph.neighbors(vnf_dst_name):
606 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
607 for link in link_dict:
peusterm53d3c142016-07-18 10:10:11 +0200608 if link_dict[link]['dst_port_id'] == vnf_dst_interface or \
609 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 +0200610 # found the right link and connected switch
611 dst_sw = connected_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200612 dst_sw_outport_nr = link_dict[link]['src_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100613 dst_sw_outport_name = link_dict[link]['src_port_name']
stevenvanrossem9315da42016-04-11 12:10:06 +0200614 break
615
jokac304ad32017-01-09 10:58:23 +0100616 path = kwargs.get('path')
617 if path is None:
618 # get shortest path
619 try:
620 # returns the first found shortest path
621 # if all shortest paths are wanted, use: all_shortest_paths
622 path = nx.shortest_path(self.DCNetwork_graph, src_sw, dst_sw, weight=kwargs.get('weight'))
623 except:
624 LOG.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
625 vnf_src_name, vnf_dst_name, src_sw, dst_sw))
626 LOG.debug("Graph nodes: %r" % self.DCNetwork_graph.nodes())
627 LOG.debug("Graph edges: %r" % self.DCNetwork_graph.edges())
628 for e, v in self.DCNetwork_graph.edges():
629 LOG.debug("%r" % self.DCNetwork_graph[e][v])
630 return "No path could be found between {0} and {1}".format(vnf_src_name, vnf_dst_name)
stevenvanrossem9315da42016-04-11 12:10:06 +0200631
peustermf9a817d2016-07-18 09:06:04 +0200632 LOG.info("Path between {0} and {1}: {2}".format(vnf_src_name, vnf_dst_name, path))
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100633
stevenvanrossem9315da42016-04-11 12:10:06 +0200634 current_hop = src_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200635 switch_inport_nr = src_sw_inport_nr
stevenvanrossem9315da42016-04-11 12:10:06 +0200636
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100637 # choose free vlan
stevenvanrossem27b6d952016-05-10 16:37:57 +0200638 cmd = kwargs.get('cmd')
639 vlan = None
stevenvanrossem263eee52017-02-08 01:04:36 +0100640 if cmd == 'add-flow':
stevenvanrossembecc7c52016-11-07 05:52:01 +0100641 if kwargs.get('tag'):
642 # use pre-defined tag
643 vlan = kwargs.get('tag')
644 else:
645 vlan = self.vlans.pop()
stevenvanrossem461941c2016-05-10 11:41:29 +0200646
stevenvanrossembecc7c52016-11-07 05:52:01 +0100647 # store the used vlan tag to identify this chain
648 if not kwargs.get('monitor'):
649 chain_dict = {}
650 chain_dict['vnf_src_name'] = vnf_src_name
651 chain_dict['vnf_dst_name'] = vnf_dst_name
652 chain_dict['vnf_src_interface'] = vnf_src_interface
653 chain_dict['vnf_dst_interface'] = vnf_dst_interface
654 chain_dict['tag'] = vlan
655 self.installed_chains.append(chain_dict)
656
657 #iterate through the path to install the flow-entries
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100658 for i in range(0,len(path)):
stevenvanrossem9315da42016-04-11 12:10:06 +0200659 current_node = self.getNodeByName(current_hop)
stevenvanrossem461941c2016-05-10 11:41:29 +0200660
jokac304ad32017-01-09 10:58:23 +0100661 if i < len(path) - 1:
662 next_hop = path[i + 1]
stevenvanrossem9315da42016-04-11 12:10:06 +0200663 else:
jokac304ad32017-01-09 10:58:23 +0100664 # last switch reached
stevenvanrossem9315da42016-04-11 12:10:06 +0200665 next_hop = vnf_dst_name
666
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100667 next_node = self.getNodeByName(next_hop)
668
669 if next_hop == vnf_dst_name:
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200670 switch_outport_nr = dst_sw_outport_nr
peustermf9a817d2016-07-18 09:06:04 +0200671 LOG.info("end node reached: {0}".format(vnf_dst_name))
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100672 elif not isinstance( next_node, OVSSwitch ):
peustermf9a817d2016-07-18 09:06:04 +0200673 LOG.info("Next node: {0} is not a switch".format(next_hop))
stevenvanrossemeefea6c2016-02-17 12:03:26 +0100674 return "Next node: {0} is not a switch".format(next_hop)
stevenvanrossem9315da42016-04-11 12:10:06 +0200675 else:
stevenvanrossemed711fd2016-04-11 16:59:29 +0200676 # take first link between switches by default
stevenvanrossem9315da42016-04-11 12:10:06 +0200677 index_edge_out = 0
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200678 switch_outport_nr = self.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
stevenvanrossem9315da42016-04-11 12:10:06 +0200679
stevenvanrossem9315da42016-04-11 12:10:06 +0200680
stevenvanrossem634c5ef2017-02-08 00:46:02 +0100681 # set OpenFlow entry
stevenvanrossem9315da42016-04-11 12:10:06 +0200682 if isinstance( current_node, OVSSwitch ):
stevenvanrossem461941c2016-05-10 11:41:29 +0200683 kwargs['vlan'] = vlan
684 kwargs['path'] = path
685 kwargs['current_hop'] = current_hop
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100686 kwargs['switch_inport_name'] = src_sw_inport_name
687 kwargs['switch_outport_name'] = dst_sw_outport_name
jokac304ad32017-01-09 10:58:23 +0100688 kwargs['pathindex'] = i
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200689
690 if self.controller == RemoteController:
691 ## set flow entry via ryu rest api
692 self._set_flow_entry_ryu_rest(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
693 else:
694 ## set flow entry via ovs-ofctl
695 self._set_flow_entry_dpctl(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
696
stevenvanrossemed711fd2016-04-11 16:59:29 +0200697 # take first link between switches by default
698 if isinstance( next_node, OVSSwitch ):
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200699 switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
stevenvanrossemed711fd2016-04-11 16:59:29 +0200700 current_hop = next_hop
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100701
stevenvanrossem27b6d952016-05-10 16:37:57 +0200702 return "path {2} between {0} and {1}".format(vnf_src_name, vnf_dst_name, cmd)
703
704 def _set_flow_entry_ryu_rest(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
705 match = 'in_port=%s' % switch_inport_nr
706
707 cookie = kwargs.get('cookie')
708 match_input = kwargs.get('match')
709 cmd = kwargs.get('cmd')
710 path = kwargs.get('path')
jokac304ad32017-01-09 10:58:23 +0100711 index = kwargs.get('pathindex')
712
stevenvanrossem27b6d952016-05-10 16:37:57 +0200713 vlan = kwargs.get('vlan')
stevenvanrossem61699eb2016-08-05 15:57:59 +0200714 priority = kwargs.get('priority')
stevenvanrossembecc7c52016-11-07 05:52:01 +0100715 # flag to not set the ovs port vlan tag
716 skip_vlan_tag = kwargs.get('skip_vlan_tag')
717 # table id to put this flowentry
718 table_id = kwargs.get('table_id')
719 if not table_id:
720 table_id = 0
stevenvanrossem27b6d952016-05-10 16:37:57 +0200721
722 s = ','
723 if match_input:
724 match = s.join([match, match_input])
725
726 flow = {}
727 flow['dpid'] = int(node.dpid, 16)
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200728
stevenvanrossem27b6d952016-05-10 16:37:57 +0200729 if cookie:
730 flow['cookie'] = int(cookie)
stevenvanrossem61699eb2016-08-05 15:57:59 +0200731 if priority:
732 flow['priority'] = int(priority)
stevenvanrossem27b6d952016-05-10 16:37:57 +0200733
stevenvanrossembecc7c52016-11-07 05:52:01 +0100734 flow['table_id'] = table_id
735
stevenvanrossem27b6d952016-05-10 16:37:57 +0200736 flow['actions'] = []
737
738 # possible Ryu actions, match fields:
739 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#add-a-flow-entry
740 if cmd == 'add-flow':
741 prefix = 'stats/flowentry/add'
stevenvanrossem27b6d952016-05-10 16:37:57 +0200742 if vlan != None:
jokac304ad32017-01-09 10:58:23 +0100743 if index == 0: # first node
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100744 # set vlan tag in ovs instance (to isolate E-LANs)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100745 if not skip_vlan_tag:
746 in_port_name = kwargs.get('switch_inport_name')
747 self._set_vlan_tag(node, in_port_name, vlan)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100748 # set vlan push action if more than 1 switch in the path
749 if len(path) > 1:
750 action = {}
751 action['type'] = 'PUSH_VLAN' # Push a new VLAN tag if a input frame is non-VLAN-tagged
752 action['ethertype'] = 33024 # Ethertype 0x8100(=33024): IEEE 802.1Q VLAN-tagged frame
753 flow['actions'].append(action)
754 action = {}
755 action['type'] = 'SET_FIELD'
756 action['field'] = 'vlan_vid'
stevenvanrossem9cc73602017-01-27 23:37:29 +0100757 # ryu expects the field to be masked
758 action['value'] = vlan | 0x1000
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100759 flow['actions'].append(action)
760
stevenvanrossem9cc73602017-01-27 23:37:29 +0100761 elif index == len(path) - 1: # last node
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100762 # set vlan tag in ovs instance (to isolate E-LANs)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100763 if not skip_vlan_tag:
764 out_port_name = kwargs.get('switch_outport_name')
765 self._set_vlan_tag(node, out_port_name, vlan)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100766 # set vlan pop action if more than 1 switch in the path
767 if len(path) > 1:
768 match += ',dl_vlan=%s' % vlan
769 action = {}
770 action['type'] = 'POP_VLAN'
771 flow['actions'].append(action)
772
stevenvanrossem27b6d952016-05-10 16:37:57 +0200773 else: # middle nodes
stevenvanrossem27b6d952016-05-10 16:37:57 +0200774 match += ',dl_vlan=%s' % vlan
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100775
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200776 # output action must come last
777 action = {}
778 action['type'] = 'OUTPUT'
779 action['port'] = switch_outport_nr
780 flow['actions'].append(action)
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200781
stevenvanrossem27b6d952016-05-10 16:37:57 +0200782 elif cmd == 'del-flows':
stevenvanrossem27b6d952016-05-10 16:37:57 +0200783 prefix = 'stats/flowentry/delete'
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200784
stevenvanrossem27b6d952016-05-10 16:37:57 +0200785 if cookie:
stevenvanrossem1ef77022016-05-12 16:36:10 +0200786 # TODO: add cookie_mask as argument
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200787 flow['cookie_mask'] = int('0xffffffffffffffff', 16) # need full mask to match complete cookie
stevenvanrossem27b6d952016-05-10 16:37:57 +0200788
789 action = {}
790 action['type'] = 'OUTPUT'
791 action['port'] = switch_outport_nr
792 flow['actions'].append(action)
793
794 flow['match'] = self._parse_match(match)
795 self.ryu_REST(prefix, data=flow)
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100796
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100797 def _set_vlan_tag(self, node, switch_port, tag):
798 node.vsctl('set', 'port {0} tag={1}'.format(switch_port,tag))
799 LOG.debug("set vlan in switch: {0} in_port: {1} vlan tag: {2}".format(node.name, switch_port, tag))
800
stevenvanrossem461941c2016-05-10 11:41:29 +0200801 def _set_flow_entry_dpctl(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100802
stevenvanrossem23c48092016-05-06 17:21:12 +0200803 match = 'in_port=%s' % switch_inport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200804
805 cookie = kwargs.get('cookie')
806 match_input = kwargs.get('match')
807 cmd = kwargs.get('cmd')
808 path = kwargs.get('path')
jokac304ad32017-01-09 10:58:23 +0100809 index = kwargs.get('pathindex')
stevenvanrossem461941c2016-05-10 11:41:29 +0200810 vlan = kwargs.get('vlan')
811
stevenvanrossem898a2af2016-05-06 18:28:57 +0200812 s = ','
813 if cookie:
814 cookie = 'cookie=%s' % cookie
815 match = s.join([cookie, match])
stevenvanrossem23c48092016-05-06 17:21:12 +0200816 if match_input:
stevenvanrossem23c48092016-05-06 17:21:12 +0200817 match = s.join([match, match_input])
stevenvanrossem23c48092016-05-06 17:21:12 +0200818 if cmd == 'add-flow':
819 action = 'action=%s' % switch_outport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200820 if vlan != None:
jokac304ad32017-01-09 10:58:23 +0100821 if index == 0: # first node
stevenvanrossem461941c2016-05-10 11:41:29 +0200822 action = ('action=mod_vlan_vid:%s' % vlan) + (',output=%s' % switch_outport_nr)
823 match = '-O OpenFlow13 ' + match
jokac304ad32017-01-09 10:58:23 +0100824 elif index == len(path) - 1: # last node
stevenvanrossem461941c2016-05-10 11:41:29 +0200825 match += ',dl_vlan=%s' % vlan
826 action = 'action=strip_vlan,output=%s' % switch_outport_nr
827 else: # middle nodes
828 match += ',dl_vlan=%s' % vlan
stevenvanrossem23c48092016-05-06 17:21:12 +0200829 ofcmd = s.join([match, action])
830 elif cmd == 'del-flows':
831 ofcmd = match
832 else:
833 ofcmd = ''
834
835 node.dpctl(cmd, ofcmd)
peustermf9a817d2016-07-18 09:06:04 +0200836 LOG.info("{3} in switch: {0} in_port: {1} out_port: {2}".format(node.name, switch_inport_nr,
stevenvanrossem461941c2016-05-10 11:41:29 +0200837 switch_outport_nr, cmd))
stevenvanrossem23c48092016-05-06 17:21:12 +0200838
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100839 # start Ryu Openflow controller as Remote Controller for the DCNetwork
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200840 def startRyu(self, learning_switch=True):
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100841 # start Ryu controller with rest-API
842 python_install_path = site.getsitepackages()[0]
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100843 # ryu default learning switch
844 #ryu_path = python_install_path + '/ryu/app/simple_switch_13.py'
845 #custom learning switch that installs a default NORMAL action in the ovs switches
846 dir_path = os.path.dirname(os.path.realpath(__file__))
847 ryu_path = dir_path + '/son_emu_simple_switch_13.py'
peustermde14f332016-03-15 16:14:21 +0100848 ryu_path2 = python_install_path + '/ryu/app/ofctl_rest.py'
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100849 # change the default Openflow controller port to 6653 (official IANA-assigned port number), as used by Mininet
850 # Ryu still uses 6633 as default
851 ryu_option = '--ofp-tcp-listen-port'
852 ryu_of_port = '6653'
peustermde14f332016-03-15 16:14:21 +0100853 ryu_cmd = 'ryu-manager'
peustermef6629e2016-03-14 17:21:56 +0100854 FNULL = open("/tmp/ryu.log", 'w')
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200855 if learning_switch:
856 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 +0100857 LOG.debug('starting ryu-controller with {0}'.format(ryu_path))
858 LOG.debug('starting ryu-controller with {0}'.format(ryu_path2))
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200859 else:
stevenvanrossem73efd192016-06-29 01:44:07 +0200860 # no learning switch, but with rest api
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200861 self.ryu_process = Popen([ryu_cmd, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100862 LOG.debug('starting ryu-controller with {0}'.format(ryu_path2))
peusterm391773a2016-03-14 17:40:43 +0100863 time.sleep(1)
864
peusterm8b04b532016-07-19 16:55:38 +0200865 def killRyu(self):
866 """
867 Stop the Ryu controller that might be started by son-emu.
868 :return:
869 """
870 # try it nicely
peustermde14f332016-03-15 16:14:21 +0100871 if self.ryu_process is not None:
peusterm391773a2016-03-14 17:40:43 +0100872 self.ryu_process.terminate()
873 self.ryu_process.kill()
peusterm8b04b532016-07-19 16:55:38 +0200874 # ensure its death ;-)
stevenvanrossem89706802016-07-19 02:54:45 +0200875 Popen(['pkill', '-f', 'ryu-manager'])
peusterm391773a2016-03-14 17:40:43 +0100876
stevenvanrossem27b6d952016-05-10 16:37:57 +0200877 def ryu_REST(self, prefix, dpid=None, data=None):
stevenvanrossem27b6d952016-05-10 16:37:57 +0200878
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200879 if dpid:
880 url = self.ryu_REST_api + '/' + str(prefix) + '/' + str(dpid)
881 else:
882 url = self.ryu_REST_api + '/' + str(prefix)
883 if data:
884 req = self.RyuSession.post(url, json=data)
885 else:
886 req = self.RyuSession.get(url)
887
888
889 # do extra logging if status code is not 200 (OK)
890 if req.status_code is not requests.codes.ok:
891 logging.info(
892 'type {0} encoding: {1} text: {2} headers: {3} history: {4}'.format(req.headers['content-type'],
893 req.encoding, req.text,
894 req.headers, req.history))
895 LOG.info('url: {0}'.format(str(url)))
896 if data: LOG.info('POST: {0}'.format(str(data)))
897 LOG.info('status: {0} reason: {1}'.format(req.status_code, req.reason))
898
899
900 if 'json' in req.headers['content-type']:
901 ret = req.json()
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200902 return ret
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200903
904 ret = req.text.rstrip()
905 return ret
906
stevenvanrossem27b6d952016-05-10 16:37:57 +0200907
908 # need to respect that some match fields must be integers
909 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#description-of-match-and-actions
910 def _parse_match(self, match):
911 matches = match.split(',')
912 dict = {}
913 for m in matches:
914 match = m.split('=')
915 if len(match) == 2:
916 try:
917 m2 = int(match[1], 0)
918 except:
919 m2 = match[1]
920
921 dict.update({match[0]:m2})
922 return dict
923
stevenvanrossem566779d2016-11-07 06:33:44 +0100924 def find_connected_dc_interface(self, vnf_src_name, vnf_src_interface):
925 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
926 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
927 for link in link_dict:
928 if (link_dict[link]['src_port_id'] == vnf_src_interface or
929 link_dict[link]['src_port_name'] == vnf_src_interface): # Fix: we might also get interface names, e.g, from a son-emu-cli call
930 # found the right link and connected switch
931 src_sw = connected_sw
932 src_sw_inport_nr = link_dict[link]['dst_port_nr']
933 src_sw_inport_name = link_dict[link]['dst_port_name']
stevenvanrossem7062cee2016-12-22 10:31:38 +0100934 return src_sw_inport_name