blob: 8973bb988220428f40d6d49b56dbe6aab0f5dd4c [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
stevenvanrossem17b6e882017-05-04 16:51:34 +020044from emuvim.dcemulator.node import Datacenter, EmulatorCompute, EmulatorExtSAP
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'])
stevenvanrossemf3712012017-05-04 00:01:52 +0200260 return Containernet.addExtSAP(self, sap_name, sap_ip, **params)
261
262 def removeExtSAP(self, sap_name, **params):
263 """
264 Wrapper for removeExtSAP method to remove SAP also from graph.
265 """
266 self.DCNetwork_graph.remove_node(sap_name)
267 return Containernet.removeExtSAP(self, sap_name)
268
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100269 def addSwitch( self, name, add_to_graph=True, **params ):
270 """
271 Wrapper for addSwitch method to store switch also in graph.
272 """
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100273
274 # add this switch to the global topology overview
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100275 if add_to_graph:
stevenvanrossemf3712012017-05-04 00:01:52 +0200276 self.DCNetwork_graph.add_node(name, type=params.get('type','switch'))
277 LOG.info('*** **** *** add switch: {0} type: {1}'.format(name, params.get('type')))
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100278
279 # set the learning switch behavior
280 if 'failMode' in params :
281 failMode = params['failMode']
282 else :
283 failMode = self.failMode
284
285 s = Containernet.addSwitch(self, name, protocols='OpenFlow10,OpenFlow12,OpenFlow13', failMode=failMode, **params)
286
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100287 return s
peustermc3b977e2016-01-12 10:09:35 +0100288
peustermbd44f4a2016-01-13 14:53:30 +0100289 def getAllContainers(self):
290 """
291 Returns a list with all containers within all data centers.
292 """
293 all_containers = []
294 for dc in self.dcs.itervalues():
295 all_containers += dc.listCompute()
296 return all_containers
297
peustermcbcd4c22015-12-28 11:33:42 +0100298 def start(self):
299 # start
300 for dc in self.dcs.itervalues():
301 dc.start()
peusterm5877ea22016-05-11 13:44:59 +0200302 Containernet.start(self)
peustermcbcd4c22015-12-28 11:33:42 +0100303
304 def stop(self):
stevenvanrossem60670da2016-04-15 15:31:28 +0200305
stevenvanrossemc6abf132016-04-14 11:15:58 +0200306 # stop the monitor agent
stevenvanrossem60670da2016-04-15 15:31:28 +0200307 if self.monitor_agent is not None:
308 self.monitor_agent.stop()
peustermcbcd4c22015-12-28 11:33:42 +0100309
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200310 # stop emulator net
peusterm5877ea22016-05-11 13:44:59 +0200311 Containernet.stop(self)
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200312
313 # stop Ryu controller
peusterm8b04b532016-07-19 16:55:38 +0200314 self.killRyu()
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200315
316
peustermcbcd4c22015-12-28 11:33:42 +0100317 def CLI(self):
peusterm293cbc32016-01-13 17:05:28 +0100318 CLI(self)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100319
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100320 def setLAN(self, vnf_list):
321 """
322 setup an E-LAN network by assigning the same VLAN tag to each DC interface of the VNFs in the E-LAN
323
324 :param vnf_list: names of the VNFs in this E-LAN [{name:,interface:},...]
325 :return:
326 """
327 src_sw = None
328 src_sw_inport_nr = 0
329 src_sw_inport_name = None
330
331 # get a vlan tag for this E-LAN
332 vlan = self.vlans.pop()
333
334 for vnf in vnf_list:
335 vnf_src_name = vnf['name']
336 vnf_src_interface = vnf['interface']
337
338 # check if port is specified (vnf:port)
339 if vnf_src_interface is None:
340 # take first interface by default
341 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
342 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
343 vnf_src_interface = link_dict[0]['src_port_id']
344
345 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
346 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
347 for link in link_dict:
348 if (link_dict[link]['src_port_id'] == vnf_src_interface or
349 link_dict[link]['src_port_name'] == vnf_src_interface): # Fix: we might also get interface names, e.g, from a son-emu-cli call
350 # found the right link and connected switch
351 src_sw = connected_sw
352 src_sw_inport_nr = link_dict[link]['dst_port_nr']
353 src_sw_inport_name = link_dict[link]['dst_port_name']
354 break
355
356 # set the tag on the dc switch interface
357 LOG.debug('set E-LAN: vnf name: {0} interface: {1} tag: {2}'.format(vnf_src_name, vnf_src_interface,vlan))
358 switch_node = self.getNodeByName(src_sw)
359 self._set_vlan_tag(switch_node, src_sw_inport_name, vlan)
360
stevenvanrossembecc7c52016-11-07 05:52:01 +0100361 def _addMonitorFlow(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None,
362 tag=None, **kwargs):
stevenvanrossembf1754e2016-11-17 10:20:52 +0100363 """
364 Add a monitoring flow entry that adds a special flowentry/counter at the begin or end of a chain.
365 So this monitoring flowrule exists on top of a previously defined chain rule and uses the same vlan tag/routing.
366 :param vnf_src_name:
367 :param vnf_dst_name:
368 :param vnf_src_interface:
369 :param vnf_dst_interface:
370 :param tag: vlan tag to be used for this chain (same tag as existing chain)
371 :param monitor_placement: 'tx' or 'rx' indicating to place the extra flowentry resp. at the beginning or end of the chain
372 :return:
373 """
stevenvanrossembecc7c52016-11-07 05:52:01 +0100374
375 src_sw = None
376 src_sw_inport_nr = 0
377 src_sw_inport_name = None
378 dst_sw = None
379 dst_sw_outport_nr = 0
380 dst_sw_outport_name = None
381
382 LOG.debug("call AddMonitorFlow vnf_src_name=%r, vnf_src_interface=%r, vnf_dst_name=%r, vnf_dst_interface=%r",
383 vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface)
384
385 #check if port is specified (vnf:port)
386 if vnf_src_interface is None:
387 # take first interface by default
388 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
389 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
390 vnf_src_interface = link_dict[0]['src_port_id']
391
392 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
393 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
394 for link in link_dict:
395 if (link_dict[link]['src_port_id'] == vnf_src_interface or
396 link_dict[link]['src_port_name'] == vnf_src_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 src_sw = connected_sw
399 src_sw_inport_nr = link_dict[link]['dst_port_nr']
400 src_sw_inport_name = link_dict[link]['dst_port_name']
401 break
402
403 if vnf_dst_interface is None:
404 # take first interface by default
405 connected_sw = self.DCNetwork_graph.neighbors(vnf_dst_name)[0]
406 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
407 vnf_dst_interface = link_dict[0]['dst_port_id']
408
409 vnf_dst_name = vnf_dst_name.split(':')[0]
410 for connected_sw in self.DCNetwork_graph.neighbors(vnf_dst_name):
411 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
412 for link in link_dict:
413 if link_dict[link]['dst_port_id'] == vnf_dst_interface or \
414 link_dict[link]['dst_port_name'] == vnf_dst_interface: # Fix: we might also get interface names, e.g, from a son-emu-cli call
415 # found the right link and connected switch
416 dst_sw = connected_sw
417 dst_sw_outport_nr = link_dict[link]['src_port_nr']
418 dst_sw_outport_name = link_dict[link]['src_port_name']
419 break
420
421 if not tag >= 0:
422 LOG.exception('tag not valid: {0}'.format(tag))
423
424 # get shortest path
425 try:
426 # returns the first found shortest path
427 # if all shortest paths are wanted, use: all_shortest_paths
428 path = nx.shortest_path(self.DCNetwork_graph, src_sw, dst_sw, weight=kwargs.get('weight'))
429 except:
430 LOG.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
431 vnf_src_name, vnf_dst_name, src_sw, dst_sw))
432 LOG.debug("Graph nodes: %r" % self.DCNetwork_graph.nodes())
433 LOG.debug("Graph edges: %r" % self.DCNetwork_graph.edges())
434 for e, v in self.DCNetwork_graph.edges():
435 LOG.debug("%r" % self.DCNetwork_graph[e][v])
436 return "No path could be found between {0} and {1}".format(vnf_src_name, vnf_dst_name)
437
438 LOG.info("Path between {0} and {1}: {2}".format(vnf_src_name, vnf_dst_name, path))
439
440 current_hop = src_sw
441 switch_inport_nr = src_sw_inport_nr
442
443 cmd = kwargs.get('cmd')
444
445 #iterate through the path to install the flow-entries
446 for i in range(0,len(path)):
447 current_node = self.getNodeByName(current_hop)
448
449 if path.index(current_hop) < len(path)-1:
450 next_hop = path[path.index(current_hop)+1]
451 else:
452 #last switch reached
453 next_hop = vnf_dst_name
454
455 next_node = self.getNodeByName(next_hop)
456
457 if next_hop == vnf_dst_name:
458 switch_outport_nr = dst_sw_outport_nr
459 LOG.info("end node reached: {0}".format(vnf_dst_name))
460 elif not isinstance( next_node, OVSSwitch ):
461 LOG.info("Next node: {0} is not a switch".format(next_hop))
462 return "Next node: {0} is not a switch".format(next_hop)
463 else:
464 # take first link between switches by default
465 index_edge_out = 0
466 switch_outport_nr = self.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100467
468
stevenvanrossembecc7c52016-11-07 05:52:01 +0100469 # set of entry via ovs-ofctl
470 if isinstance( current_node, OVSSwitch ):
471 kwargs['vlan'] = tag
472 kwargs['path'] = path
473 kwargs['current_hop'] = current_hop
474 kwargs['switch_inport_name'] = src_sw_inport_name
475 kwargs['switch_outport_name'] = dst_sw_outport_name
476 kwargs['skip_vlan_tag'] = True
stevenvanrossem263eee52017-02-08 01:04:36 +0100477 kwargs['pathindex'] = i
stevenvanrossembecc7c52016-11-07 05:52:01 +0100478
stevenvanrossem4fac2af2016-12-22 01:26:02 +0100479 monitor_placement = kwargs.get('monitor_placement').strip()
stevenvanrossembecc7c52016-11-07 05:52:01 +0100480 # put monitor flow at the dst switch
481 insert_flow = False
482 if monitor_placement == 'tx' and path.index(current_hop) == 0: # first node:
483 insert_flow = True
484 # put monitoring flow at the src switch
485 elif monitor_placement == 'rx' and path.index(current_hop) == len(path) - 1: # last node:
486 insert_flow = True
stevenvanrossem4fac2af2016-12-22 01:26:02 +0100487 elif monitor_placement not in ['rx', 'tx']:
stevenvanrossembecc7c52016-11-07 05:52:01 +0100488 LOG.exception('invalid monitor command: {0}'.format(monitor_placement))
489
490
491 if self.controller == RemoteController and insert_flow:
492 ## set flow entry via ryu rest api
493 self._set_flow_entry_ryu_rest(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
494 break
495 elif insert_flow:
496 ## set flow entry via ovs-ofctl
497 self._set_flow_entry_dpctl(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
498 break
499
500 # take first link between switches by default
501 if isinstance( next_node, OVSSwitch ):
502 switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
503 current_hop = next_hop
504
505 return "path {2} between {0} and {1}".format(vnf_src_name, vnf_dst_name, cmd)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100506
507
stevenvanrossem461941c2016-05-10 11:41:29 +0200508 def setChain(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200509 """
510 Chain 2 vnf interfaces together by installing the flowrules in the switches along their path.
511 Currently the path is found using the default networkx shortest path function.
512 Each chain gets a unique vlan id , so different chains wil not interfere.
513
514 :param vnf_src_name: vnf name (string)
515 :param vnf_dst_name: vnf name (string)
516 :param vnf_src_interface: source interface name (string)
517 :param vnf_dst_interface: destination interface name (string)
518 :param cmd: 'add-flow' (default) to add a chain, 'del-flows' to remove a chain
519 :param cookie: cookie for the installed flowrules (can be used later as identifier for a set of installed chains)
520 :param match: custom match entry to be added to the flowrules (default: only in_port and vlan tag)
521 :param priority: custom flowrule priority
stevenvanrossembf1754e2016-11-17 10:20:52 +0100522 :param monitor: boolean to indicate whether this chain is a monitoring chain
523 :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 +0100524 :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 +0100525 :param path: custom path between the two VNFs (list of switches)
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200526 :return: output log string
527 """
stevenvanrossembecc7c52016-11-07 05:52:01 +0100528
529 # special procedure for monitoring flows
530 if kwargs.get('monitor'):
531
532 # check if chain already exists
533 found_chains = [chain_dict for chain_dict in self.installed_chains if
534 (chain_dict['vnf_src_name'] == vnf_src_name and chain_dict['vnf_src_interface'] == vnf_src_interface
535 and chain_dict['vnf_dst_name'] == vnf_dst_name and chain_dict['vnf_dst_interface'] == vnf_dst_interface)]
536
537 if len(found_chains) > 0:
538 # this chain exists, so need an extra monitoring flow
539 # assume only 1 chain per vnf/interface pair
540 LOG.debug('*** installing monitoring chain on top of pre-defined chain from {0}:{1} -> {2}:{3}'.
541 format(vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface))
542 tag = found_chains[0]['tag']
543 ret = self._addMonitorFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface,
544 tag=tag, table_id=0, **kwargs)
545 return ret
546 else:
547 # no chain existing (or E-LAN) -> install normal chain
stevenvanrossem4fac2af2016-12-22 01:26:02 +0100548 LOG.warning('*** installing monitoring chain without pre-defined NSD chain from {0}:{1} -> {2}:{3}'.
stevenvanrossembecc7c52016-11-07 05:52:01 +0100549 format(vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface))
550 pass
551
552
stevenvanrossem461941c2016-05-10 11:41:29 +0200553 cmd = kwargs.get('cmd')
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100554 if cmd == 'add-flow' or cmd == 'del-flows':
stevenvanrossem461941c2016-05-10 11:41:29 +0200555 ret = self._chainAddFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface, **kwargs)
556 if kwargs.get('bidirectional'):
jokac304ad32017-01-09 10:58:23 +0100557 if kwargs.get('path') is not None:
558 kwargs['path'] = list(reversed(kwargs.get('path')))
stevenvanrossem81955a52016-05-12 14:34:12 +0200559 ret = ret +'\n' + self._chainAddFlow(vnf_dst_name, vnf_src_name, vnf_dst_interface, vnf_src_interface, **kwargs)
stevenvanrossem9315da42016-04-11 12:10:06 +0200560
stevenvanrossem461941c2016-05-10 11:41:29 +0200561 else:
stevenvanrossem81955a52016-05-12 14:34:12 +0200562 ret = "Command unknown"
563
564 return ret
stevenvanrossem461941c2016-05-10 11:41:29 +0200565
566
567 def _chainAddFlow(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
568
peusterm53d3c142016-07-18 10:10:11 +0200569 src_sw = None
peusterm53d3c142016-07-18 10:10:11 +0200570 src_sw_inport_nr = 0
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100571 src_sw_inport_name = None
572 dst_sw = None
peusterm53d3c142016-07-18 10:10:11 +0200573 dst_sw_outport_nr = 0
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100574 dst_sw_outport_name = None
peusterm53d3c142016-07-18 10:10:11 +0200575
576 LOG.debug("call chainAddFlow vnf_src_name=%r, vnf_src_interface=%r, vnf_dst_name=%r, vnf_dst_interface=%r",
577 vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface)
578
stevenvanrossem9315da42016-04-11 12:10:06 +0200579 #check if port is specified (vnf:port)
stevenvanrossemed711fd2016-04-11 16:59:29 +0200580 if vnf_src_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200581 # take first interface by default
582 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
583 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200584 vnf_src_interface = link_dict[0]['src_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200585
stevenvanrossem9315da42016-04-11 12:10:06 +0200586 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
587 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
588 for link in link_dict:
peusterm53d3c142016-07-18 10:10:11 +0200589 if (link_dict[link]['src_port_id'] == vnf_src_interface or
590 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 +0200591 # found the right link and connected switch
stevenvanrossem9315da42016-04-11 12:10:06 +0200592 src_sw = connected_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200593 src_sw_inport_nr = link_dict[link]['dst_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100594 src_sw_inport_name = link_dict[link]['dst_port_name']
stevenvanrossem9315da42016-04-11 12:10:06 +0200595 break
596
stevenvanrossemed711fd2016-04-11 16:59:29 +0200597 if vnf_dst_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200598 # take first interface by default
599 connected_sw = self.DCNetwork_graph.neighbors(vnf_dst_name)[0]
600 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200601 vnf_dst_interface = link_dict[0]['dst_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200602
603 vnf_dst_name = vnf_dst_name.split(':')[0]
604 for connected_sw in self.DCNetwork_graph.neighbors(vnf_dst_name):
605 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
606 for link in link_dict:
peusterm53d3c142016-07-18 10:10:11 +0200607 if link_dict[link]['dst_port_id'] == vnf_dst_interface or \
608 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 +0200609 # found the right link and connected switch
610 dst_sw = connected_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200611 dst_sw_outport_nr = link_dict[link]['src_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100612 dst_sw_outport_name = link_dict[link]['src_port_name']
stevenvanrossem9315da42016-04-11 12:10:06 +0200613 break
614
jokac304ad32017-01-09 10:58:23 +0100615 path = kwargs.get('path')
616 if path is None:
617 # get shortest path
618 try:
619 # returns the first found shortest path
620 # if all shortest paths are wanted, use: all_shortest_paths
621 path = nx.shortest_path(self.DCNetwork_graph, src_sw, dst_sw, weight=kwargs.get('weight'))
622 except:
623 LOG.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
624 vnf_src_name, vnf_dst_name, src_sw, dst_sw))
625 LOG.debug("Graph nodes: %r" % self.DCNetwork_graph.nodes())
626 LOG.debug("Graph edges: %r" % self.DCNetwork_graph.edges())
627 for e, v in self.DCNetwork_graph.edges():
628 LOG.debug("%r" % self.DCNetwork_graph[e][v])
629 return "No path could be found between {0} and {1}".format(vnf_src_name, vnf_dst_name)
stevenvanrossem9315da42016-04-11 12:10:06 +0200630
peustermf9a817d2016-07-18 09:06:04 +0200631 LOG.info("Path between {0} and {1}: {2}".format(vnf_src_name, vnf_dst_name, path))
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100632
stevenvanrossem9315da42016-04-11 12:10:06 +0200633 current_hop = src_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200634 switch_inport_nr = src_sw_inport_nr
stevenvanrossem9315da42016-04-11 12:10:06 +0200635
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100636 # choose free vlan
stevenvanrossem27b6d952016-05-10 16:37:57 +0200637 cmd = kwargs.get('cmd')
638 vlan = None
stevenvanrossem263eee52017-02-08 01:04:36 +0100639 if cmd == 'add-flow':
stevenvanrossembecc7c52016-11-07 05:52:01 +0100640 if kwargs.get('tag'):
641 # use pre-defined tag
642 vlan = kwargs.get('tag')
643 else:
644 vlan = self.vlans.pop()
stevenvanrossem461941c2016-05-10 11:41:29 +0200645
stevenvanrossembecc7c52016-11-07 05:52:01 +0100646 # store the used vlan tag to identify this chain
647 if not kwargs.get('monitor'):
648 chain_dict = {}
649 chain_dict['vnf_src_name'] = vnf_src_name
650 chain_dict['vnf_dst_name'] = vnf_dst_name
651 chain_dict['vnf_src_interface'] = vnf_src_interface
652 chain_dict['vnf_dst_interface'] = vnf_dst_interface
653 chain_dict['tag'] = vlan
654 self.installed_chains.append(chain_dict)
655
656 #iterate through the path to install the flow-entries
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100657 for i in range(0,len(path)):
stevenvanrossem9315da42016-04-11 12:10:06 +0200658 current_node = self.getNodeByName(current_hop)
stevenvanrossem461941c2016-05-10 11:41:29 +0200659
jokac304ad32017-01-09 10:58:23 +0100660 if i < len(path) - 1:
661 next_hop = path[i + 1]
stevenvanrossem9315da42016-04-11 12:10:06 +0200662 else:
jokac304ad32017-01-09 10:58:23 +0100663 # last switch reached
stevenvanrossem9315da42016-04-11 12:10:06 +0200664 next_hop = vnf_dst_name
665
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100666 next_node = self.getNodeByName(next_hop)
667
668 if next_hop == vnf_dst_name:
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200669 switch_outport_nr = dst_sw_outport_nr
peustermf9a817d2016-07-18 09:06:04 +0200670 LOG.info("end node reached: {0}".format(vnf_dst_name))
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100671 elif not isinstance( next_node, OVSSwitch ):
peustermf9a817d2016-07-18 09:06:04 +0200672 LOG.info("Next node: {0} is not a switch".format(next_hop))
stevenvanrossemeefea6c2016-02-17 12:03:26 +0100673 return "Next node: {0} is not a switch".format(next_hop)
stevenvanrossem9315da42016-04-11 12:10:06 +0200674 else:
stevenvanrossemed711fd2016-04-11 16:59:29 +0200675 # take first link between switches by default
stevenvanrossem9315da42016-04-11 12:10:06 +0200676 index_edge_out = 0
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200677 switch_outport_nr = self.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
stevenvanrossem9315da42016-04-11 12:10:06 +0200678
stevenvanrossem9315da42016-04-11 12:10:06 +0200679
stevenvanrossem634c5ef2017-02-08 00:46:02 +0100680 # set OpenFlow entry
stevenvanrossem9315da42016-04-11 12:10:06 +0200681 if isinstance( current_node, OVSSwitch ):
stevenvanrossem461941c2016-05-10 11:41:29 +0200682 kwargs['vlan'] = vlan
683 kwargs['path'] = path
684 kwargs['current_hop'] = current_hop
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100685 kwargs['switch_inport_name'] = src_sw_inport_name
686 kwargs['switch_outport_name'] = dst_sw_outport_name
jokac304ad32017-01-09 10:58:23 +0100687 kwargs['pathindex'] = i
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200688
689 if self.controller == RemoteController:
690 ## set flow entry via ryu rest api
691 self._set_flow_entry_ryu_rest(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
692 else:
693 ## set flow entry via ovs-ofctl
694 self._set_flow_entry_dpctl(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
695
stevenvanrossemed711fd2016-04-11 16:59:29 +0200696 # take first link between switches by default
697 if isinstance( next_node, OVSSwitch ):
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200698 switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
stevenvanrossemed711fd2016-04-11 16:59:29 +0200699 current_hop = next_hop
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100700
stevenvanrossem27b6d952016-05-10 16:37:57 +0200701 return "path {2} between {0} and {1}".format(vnf_src_name, vnf_dst_name, cmd)
702
703 def _set_flow_entry_ryu_rest(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
704 match = 'in_port=%s' % switch_inport_nr
705
706 cookie = kwargs.get('cookie')
707 match_input = kwargs.get('match')
708 cmd = kwargs.get('cmd')
709 path = kwargs.get('path')
jokac304ad32017-01-09 10:58:23 +0100710 index = kwargs.get('pathindex')
711
stevenvanrossem27b6d952016-05-10 16:37:57 +0200712 vlan = kwargs.get('vlan')
stevenvanrossem61699eb2016-08-05 15:57:59 +0200713 priority = kwargs.get('priority')
stevenvanrossembecc7c52016-11-07 05:52:01 +0100714 # flag to not set the ovs port vlan tag
715 skip_vlan_tag = kwargs.get('skip_vlan_tag')
716 # table id to put this flowentry
717 table_id = kwargs.get('table_id')
718 if not table_id:
719 table_id = 0
stevenvanrossem27b6d952016-05-10 16:37:57 +0200720
721 s = ','
722 if match_input:
723 match = s.join([match, match_input])
724
725 flow = {}
726 flow['dpid'] = int(node.dpid, 16)
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200727
stevenvanrossem27b6d952016-05-10 16:37:57 +0200728 if cookie:
729 flow['cookie'] = int(cookie)
stevenvanrossem61699eb2016-08-05 15:57:59 +0200730 if priority:
731 flow['priority'] = int(priority)
stevenvanrossem27b6d952016-05-10 16:37:57 +0200732
stevenvanrossembecc7c52016-11-07 05:52:01 +0100733 flow['table_id'] = table_id
734
stevenvanrossem27b6d952016-05-10 16:37:57 +0200735 flow['actions'] = []
736
737 # possible Ryu actions, match fields:
738 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#add-a-flow-entry
739 if cmd == 'add-flow':
740 prefix = 'stats/flowentry/add'
stevenvanrossem27b6d952016-05-10 16:37:57 +0200741 if vlan != None:
jokac304ad32017-01-09 10:58:23 +0100742 if index == 0: # first 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 in_port_name = kwargs.get('switch_inport_name')
746 self._set_vlan_tag(node, in_port_name, vlan)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100747 # set vlan push action if more than 1 switch in the path
748 if len(path) > 1:
749 action = {}
750 action['type'] = 'PUSH_VLAN' # Push a new VLAN tag if a input frame is non-VLAN-tagged
751 action['ethertype'] = 33024 # Ethertype 0x8100(=33024): IEEE 802.1Q VLAN-tagged frame
752 flow['actions'].append(action)
753 action = {}
754 action['type'] = 'SET_FIELD'
755 action['field'] = 'vlan_vid'
stevenvanrossem9cc73602017-01-27 23:37:29 +0100756 # ryu expects the field to be masked
757 action['value'] = vlan | 0x1000
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100758 flow['actions'].append(action)
759
stevenvanrossem9cc73602017-01-27 23:37:29 +0100760 elif index == len(path) - 1: # last node
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100761 # set vlan tag in ovs instance (to isolate E-LANs)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100762 if not skip_vlan_tag:
763 out_port_name = kwargs.get('switch_outport_name')
764 self._set_vlan_tag(node, out_port_name, vlan)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100765 # set vlan pop action if more than 1 switch in the path
766 if len(path) > 1:
767 match += ',dl_vlan=%s' % vlan
768 action = {}
769 action['type'] = 'POP_VLAN'
770 flow['actions'].append(action)
771
stevenvanrossem27b6d952016-05-10 16:37:57 +0200772 else: # middle nodes
stevenvanrossem27b6d952016-05-10 16:37:57 +0200773 match += ',dl_vlan=%s' % vlan
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100774
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200775 # output action must come last
776 action = {}
777 action['type'] = 'OUTPUT'
778 action['port'] = switch_outport_nr
779 flow['actions'].append(action)
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200780
stevenvanrossem27b6d952016-05-10 16:37:57 +0200781 elif cmd == 'del-flows':
stevenvanrossem27b6d952016-05-10 16:37:57 +0200782 prefix = 'stats/flowentry/delete'
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200783
stevenvanrossem27b6d952016-05-10 16:37:57 +0200784 if cookie:
stevenvanrossem1ef77022016-05-12 16:36:10 +0200785 # TODO: add cookie_mask as argument
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200786 flow['cookie_mask'] = int('0xffffffffffffffff', 16) # need full mask to match complete cookie
stevenvanrossem27b6d952016-05-10 16:37:57 +0200787
788 action = {}
789 action['type'] = 'OUTPUT'
790 action['port'] = switch_outport_nr
791 flow['actions'].append(action)
792
793 flow['match'] = self._parse_match(match)
794 self.ryu_REST(prefix, data=flow)
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100795
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100796 def _set_vlan_tag(self, node, switch_port, tag):
797 node.vsctl('set', 'port {0} tag={1}'.format(switch_port,tag))
798 LOG.debug("set vlan in switch: {0} in_port: {1} vlan tag: {2}".format(node.name, switch_port, tag))
799
stevenvanrossem461941c2016-05-10 11:41:29 +0200800 def _set_flow_entry_dpctl(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100801
stevenvanrossem23c48092016-05-06 17:21:12 +0200802 match = 'in_port=%s' % switch_inport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200803
804 cookie = kwargs.get('cookie')
805 match_input = kwargs.get('match')
806 cmd = kwargs.get('cmd')
807 path = kwargs.get('path')
jokac304ad32017-01-09 10:58:23 +0100808 index = kwargs.get('pathindex')
stevenvanrossem461941c2016-05-10 11:41:29 +0200809 vlan = kwargs.get('vlan')
810
stevenvanrossem898a2af2016-05-06 18:28:57 +0200811 s = ','
812 if cookie:
813 cookie = 'cookie=%s' % cookie
814 match = s.join([cookie, match])
stevenvanrossem23c48092016-05-06 17:21:12 +0200815 if match_input:
stevenvanrossem23c48092016-05-06 17:21:12 +0200816 match = s.join([match, match_input])
stevenvanrossem23c48092016-05-06 17:21:12 +0200817 if cmd == 'add-flow':
818 action = 'action=%s' % switch_outport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200819 if vlan != None:
jokac304ad32017-01-09 10:58:23 +0100820 if index == 0: # first node
stevenvanrossem461941c2016-05-10 11:41:29 +0200821 action = ('action=mod_vlan_vid:%s' % vlan) + (',output=%s' % switch_outport_nr)
822 match = '-O OpenFlow13 ' + match
jokac304ad32017-01-09 10:58:23 +0100823 elif index == len(path) - 1: # last node
stevenvanrossem461941c2016-05-10 11:41:29 +0200824 match += ',dl_vlan=%s' % vlan
825 action = 'action=strip_vlan,output=%s' % switch_outport_nr
826 else: # middle nodes
827 match += ',dl_vlan=%s' % vlan
stevenvanrossem23c48092016-05-06 17:21:12 +0200828 ofcmd = s.join([match, action])
829 elif cmd == 'del-flows':
830 ofcmd = match
831 else:
832 ofcmd = ''
833
834 node.dpctl(cmd, ofcmd)
peustermf9a817d2016-07-18 09:06:04 +0200835 LOG.info("{3} in switch: {0} in_port: {1} out_port: {2}".format(node.name, switch_inport_nr,
stevenvanrossem461941c2016-05-10 11:41:29 +0200836 switch_outport_nr, cmd))
stevenvanrossem23c48092016-05-06 17:21:12 +0200837
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100838 # start Ryu Openflow controller as Remote Controller for the DCNetwork
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200839 def startRyu(self, learning_switch=True):
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100840 # start Ryu controller with rest-API
841 python_install_path = site.getsitepackages()[0]
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100842 # ryu default learning switch
843 #ryu_path = python_install_path + '/ryu/app/simple_switch_13.py'
844 #custom learning switch that installs a default NORMAL action in the ovs switches
845 dir_path = os.path.dirname(os.path.realpath(__file__))
846 ryu_path = dir_path + '/son_emu_simple_switch_13.py'
peustermde14f332016-03-15 16:14:21 +0100847 ryu_path2 = python_install_path + '/ryu/app/ofctl_rest.py'
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100848 # change the default Openflow controller port to 6653 (official IANA-assigned port number), as used by Mininet
849 # Ryu still uses 6633 as default
850 ryu_option = '--ofp-tcp-listen-port'
851 ryu_of_port = '6653'
peustermde14f332016-03-15 16:14:21 +0100852 ryu_cmd = 'ryu-manager'
peustermef6629e2016-03-14 17:21:56 +0100853 FNULL = open("/tmp/ryu.log", 'w')
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200854 if learning_switch:
855 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 +0100856 LOG.debug('starting ryu-controller with {0}'.format(ryu_path))
857 LOG.debug('starting ryu-controller with {0}'.format(ryu_path2))
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200858 else:
stevenvanrossem73efd192016-06-29 01:44:07 +0200859 # no learning switch, but with rest api
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200860 self.ryu_process = Popen([ryu_cmd, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100861 LOG.debug('starting ryu-controller with {0}'.format(ryu_path2))
peusterm391773a2016-03-14 17:40:43 +0100862 time.sleep(1)
863
peusterm8b04b532016-07-19 16:55:38 +0200864 def killRyu(self):
865 """
866 Stop the Ryu controller that might be started by son-emu.
867 :return:
868 """
869 # try it nicely
peustermde14f332016-03-15 16:14:21 +0100870 if self.ryu_process is not None:
peusterm391773a2016-03-14 17:40:43 +0100871 self.ryu_process.terminate()
872 self.ryu_process.kill()
peusterm8b04b532016-07-19 16:55:38 +0200873 # ensure its death ;-)
stevenvanrossem89706802016-07-19 02:54:45 +0200874 Popen(['pkill', '-f', 'ryu-manager'])
peusterm391773a2016-03-14 17:40:43 +0100875
stevenvanrossem27b6d952016-05-10 16:37:57 +0200876 def ryu_REST(self, prefix, dpid=None, data=None):
stevenvanrossem27b6d952016-05-10 16:37:57 +0200877
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200878 if dpid:
879 url = self.ryu_REST_api + '/' + str(prefix) + '/' + str(dpid)
880 else:
881 url = self.ryu_REST_api + '/' + str(prefix)
882 if data:
883 req = self.RyuSession.post(url, json=data)
884 else:
885 req = self.RyuSession.get(url)
886
887
888 # do extra logging if status code is not 200 (OK)
889 if req.status_code is not requests.codes.ok:
890 logging.info(
891 'type {0} encoding: {1} text: {2} headers: {3} history: {4}'.format(req.headers['content-type'],
892 req.encoding, req.text,
893 req.headers, req.history))
894 LOG.info('url: {0}'.format(str(url)))
895 if data: LOG.info('POST: {0}'.format(str(data)))
896 LOG.info('status: {0} reason: {1}'.format(req.status_code, req.reason))
897
898
899 if 'json' in req.headers['content-type']:
900 ret = req.json()
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200901 return ret
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200902
903 ret = req.text.rstrip()
904 return ret
905
stevenvanrossem27b6d952016-05-10 16:37:57 +0200906
907 # need to respect that some match fields must be integers
908 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#description-of-match-and-actions
909 def _parse_match(self, match):
910 matches = match.split(',')
911 dict = {}
912 for m in matches:
913 match = m.split('=')
914 if len(match) == 2:
915 try:
916 m2 = int(match[1], 0)
917 except:
918 m2 = match[1]
919
920 dict.update({match[0]:m2})
921 return dict
922
stevenvanrossem17b6e882017-05-04 16:51:34 +0200923 def find_connected_dc_interface(self, vnf_src_name, vnf_src_interface=None):
924
925 if vnf_src_interface is None:
926 # take first interface by default
927 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
928 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
929 vnf_src_interface = link_dict[0]['src_port_id']
930
stevenvanrossem566779d2016-11-07 06:33:44 +0100931 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
932 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
933 for link in link_dict:
934 if (link_dict[link]['src_port_id'] == vnf_src_interface or
935 link_dict[link]['src_port_name'] == vnf_src_interface): # Fix: we might also get interface names, e.g, from a son-emu-cli call
936 # found the right link and connected switch
937 src_sw = connected_sw
938 src_sw_inport_nr = link_dict[link]['dst_port_nr']
939 src_sw_inport_name = link_dict[link]['dst_port_name']
stevenvanrossem7062cee2016-12-22 10:31:38 +0100940 return src_sw_inport_name