blob: 9dfef3655af7cc5a0990160603e50c1ec8f51e0e [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'))
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100277
278 # set the learning switch behavior
279 if 'failMode' in params :
280 failMode = params['failMode']
281 else :
282 failMode = self.failMode
283
284 s = Containernet.addSwitch(self, name, protocols='OpenFlow10,OpenFlow12,OpenFlow13', failMode=failMode, **params)
285
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100286 return s
peustermc3b977e2016-01-12 10:09:35 +0100287
peustermbd44f4a2016-01-13 14:53:30 +0100288 def getAllContainers(self):
289 """
290 Returns a list with all containers within all data centers.
291 """
292 all_containers = []
293 for dc in self.dcs.itervalues():
294 all_containers += dc.listCompute()
295 return all_containers
296
peustermcbcd4c22015-12-28 11:33:42 +0100297 def start(self):
298 # start
299 for dc in self.dcs.itervalues():
300 dc.start()
peusterm5877ea22016-05-11 13:44:59 +0200301 Containernet.start(self)
peustermcbcd4c22015-12-28 11:33:42 +0100302
303 def stop(self):
stevenvanrossem60670da2016-04-15 15:31:28 +0200304
stevenvanrossemc6abf132016-04-14 11:15:58 +0200305 # stop the monitor agent
stevenvanrossem60670da2016-04-15 15:31:28 +0200306 if self.monitor_agent is not None:
307 self.monitor_agent.stop()
peustermcbcd4c22015-12-28 11:33:42 +0100308
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200309 # stop emulator net
peusterm5877ea22016-05-11 13:44:59 +0200310 Containernet.stop(self)
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200311
312 # stop Ryu controller
peusterm8b04b532016-07-19 16:55:38 +0200313 self.killRyu()
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200314
315
peustermcbcd4c22015-12-28 11:33:42 +0100316 def CLI(self):
peusterm293cbc32016-01-13 17:05:28 +0100317 CLI(self)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100318
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100319 def setLAN(self, vnf_list):
320 """
321 setup an E-LAN network by assigning the same VLAN tag to each DC interface of the VNFs in the E-LAN
322
323 :param vnf_list: names of the VNFs in this E-LAN [{name:,interface:},...]
324 :return:
325 """
326 src_sw = None
327 src_sw_inport_nr = 0
328 src_sw_inport_name = None
329
330 # get a vlan tag for this E-LAN
331 vlan = self.vlans.pop()
332
333 for vnf in vnf_list:
334 vnf_src_name = vnf['name']
335 vnf_src_interface = vnf['interface']
336
337 # check if port is specified (vnf:port)
338 if vnf_src_interface is None:
339 # take first interface by default
340 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
341 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
342 vnf_src_interface = link_dict[0]['src_port_id']
343
344 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
345 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
346 for link in link_dict:
347 if (link_dict[link]['src_port_id'] == vnf_src_interface or
348 link_dict[link]['src_port_name'] == vnf_src_interface): # Fix: we might also get interface names, e.g, from a son-emu-cli call
349 # found the right link and connected switch
350 src_sw = connected_sw
351 src_sw_inport_nr = link_dict[link]['dst_port_nr']
352 src_sw_inport_name = link_dict[link]['dst_port_name']
353 break
354
355 # set the tag on the dc switch interface
356 LOG.debug('set E-LAN: vnf name: {0} interface: {1} tag: {2}'.format(vnf_src_name, vnf_src_interface,vlan))
357 switch_node = self.getNodeByName(src_sw)
358 self._set_vlan_tag(switch_node, src_sw_inport_name, vlan)
359
stevenvanrossembecc7c52016-11-07 05:52:01 +0100360 def _addMonitorFlow(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None,
361 tag=None, **kwargs):
stevenvanrossembf1754e2016-11-17 10:20:52 +0100362 """
363 Add a monitoring flow entry that adds a special flowentry/counter at the begin or end of a chain.
364 So this monitoring flowrule exists on top of a previously defined chain rule and uses the same vlan tag/routing.
365 :param vnf_src_name:
366 :param vnf_dst_name:
367 :param vnf_src_interface:
368 :param vnf_dst_interface:
369 :param tag: vlan tag to be used for this chain (same tag as existing chain)
370 :param monitor_placement: 'tx' or 'rx' indicating to place the extra flowentry resp. at the beginning or end of the chain
371 :return:
372 """
stevenvanrossembecc7c52016-11-07 05:52:01 +0100373
374 src_sw = None
375 src_sw_inport_nr = 0
376 src_sw_inport_name = None
377 dst_sw = None
378 dst_sw_outport_nr = 0
379 dst_sw_outport_name = None
380
381 LOG.debug("call AddMonitorFlow vnf_src_name=%r, vnf_src_interface=%r, vnf_dst_name=%r, vnf_dst_interface=%r",
382 vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface)
383
384 #check if port is specified (vnf:port)
385 if vnf_src_interface is None:
386 # take first interface by default
387 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
388 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
389 vnf_src_interface = link_dict[0]['src_port_id']
390
391 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
392 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
393 for link in link_dict:
394 if (link_dict[link]['src_port_id'] == vnf_src_interface or
395 link_dict[link]['src_port_name'] == vnf_src_interface): # Fix: we might also get interface names, e.g, from a son-emu-cli call
396 # found the right link and connected switch
397 src_sw = connected_sw
398 src_sw_inport_nr = link_dict[link]['dst_port_nr']
399 src_sw_inport_name = link_dict[link]['dst_port_name']
400 break
401
402 if vnf_dst_interface is None:
403 # take first interface by default
404 connected_sw = self.DCNetwork_graph.neighbors(vnf_dst_name)[0]
405 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
406 vnf_dst_interface = link_dict[0]['dst_port_id']
407
408 vnf_dst_name = vnf_dst_name.split(':')[0]
409 for connected_sw in self.DCNetwork_graph.neighbors(vnf_dst_name):
410 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
411 for link in link_dict:
412 if link_dict[link]['dst_port_id'] == vnf_dst_interface or \
413 link_dict[link]['dst_port_name'] == vnf_dst_interface: # Fix: we might also get interface names, e.g, from a son-emu-cli call
414 # found the right link and connected switch
415 dst_sw = connected_sw
416 dst_sw_outport_nr = link_dict[link]['src_port_nr']
417 dst_sw_outport_name = link_dict[link]['src_port_name']
418 break
419
420 if not tag >= 0:
421 LOG.exception('tag not valid: {0}'.format(tag))
422
423 # get shortest path
424 try:
425 # returns the first found shortest path
426 # if all shortest paths are wanted, use: all_shortest_paths
427 path = nx.shortest_path(self.DCNetwork_graph, src_sw, dst_sw, weight=kwargs.get('weight'))
428 except:
429 LOG.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
430 vnf_src_name, vnf_dst_name, src_sw, dst_sw))
431 LOG.debug("Graph nodes: %r" % self.DCNetwork_graph.nodes())
432 LOG.debug("Graph edges: %r" % self.DCNetwork_graph.edges())
433 for e, v in self.DCNetwork_graph.edges():
434 LOG.debug("%r" % self.DCNetwork_graph[e][v])
435 return "No path could be found between {0} and {1}".format(vnf_src_name, vnf_dst_name)
436
437 LOG.info("Path between {0} and {1}: {2}".format(vnf_src_name, vnf_dst_name, path))
438
439 current_hop = src_sw
440 switch_inport_nr = src_sw_inport_nr
441
442 cmd = kwargs.get('cmd')
443
444 #iterate through the path to install the flow-entries
445 for i in range(0,len(path)):
446 current_node = self.getNodeByName(current_hop)
447
448 if path.index(current_hop) < len(path)-1:
449 next_hop = path[path.index(current_hop)+1]
450 else:
451 #last switch reached
452 next_hop = vnf_dst_name
453
454 next_node = self.getNodeByName(next_hop)
455
456 if next_hop == vnf_dst_name:
457 switch_outport_nr = dst_sw_outport_nr
458 LOG.info("end node reached: {0}".format(vnf_dst_name))
459 elif not isinstance( next_node, OVSSwitch ):
460 LOG.info("Next node: {0} is not a switch".format(next_hop))
461 return "Next node: {0} is not a switch".format(next_hop)
462 else:
463 # take first link between switches by default
464 index_edge_out = 0
465 switch_outport_nr = self.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100466
467
stevenvanrossembecc7c52016-11-07 05:52:01 +0100468 # set of entry via ovs-ofctl
469 if isinstance( current_node, OVSSwitch ):
470 kwargs['vlan'] = tag
471 kwargs['path'] = path
472 kwargs['current_hop'] = current_hop
473 kwargs['switch_inport_name'] = src_sw_inport_name
474 kwargs['switch_outport_name'] = dst_sw_outport_name
475 kwargs['skip_vlan_tag'] = True
stevenvanrossem263eee52017-02-08 01:04:36 +0100476 kwargs['pathindex'] = i
stevenvanrossembecc7c52016-11-07 05:52:01 +0100477
stevenvanrossem4fac2af2016-12-22 01:26:02 +0100478 monitor_placement = kwargs.get('monitor_placement').strip()
stevenvanrossembecc7c52016-11-07 05:52:01 +0100479 # put monitor flow at the dst switch
480 insert_flow = False
481 if monitor_placement == 'tx' and path.index(current_hop) == 0: # first node:
482 insert_flow = True
483 # put monitoring flow at the src switch
484 elif monitor_placement == 'rx' and path.index(current_hop) == len(path) - 1: # last node:
485 insert_flow = True
stevenvanrossem4fac2af2016-12-22 01:26:02 +0100486 elif monitor_placement not in ['rx', 'tx']:
stevenvanrossembecc7c52016-11-07 05:52:01 +0100487 LOG.exception('invalid monitor command: {0}'.format(monitor_placement))
488
489
490 if self.controller == RemoteController and insert_flow:
491 ## set flow entry via ryu rest api
492 self._set_flow_entry_ryu_rest(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
493 break
494 elif insert_flow:
495 ## set flow entry via ovs-ofctl
496 self._set_flow_entry_dpctl(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
497 break
498
499 # take first link between switches by default
500 if isinstance( next_node, OVSSwitch ):
501 switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
502 current_hop = next_hop
503
504 return "path {2} between {0} and {1}".format(vnf_src_name, vnf_dst_name, cmd)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100505
506
stevenvanrossem461941c2016-05-10 11:41:29 +0200507 def setChain(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200508 """
509 Chain 2 vnf interfaces together by installing the flowrules in the switches along their path.
510 Currently the path is found using the default networkx shortest path function.
511 Each chain gets a unique vlan id , so different chains wil not interfere.
512
513 :param vnf_src_name: vnf name (string)
514 :param vnf_dst_name: vnf name (string)
515 :param vnf_src_interface: source interface name (string)
516 :param vnf_dst_interface: destination interface name (string)
517 :param cmd: 'add-flow' (default) to add a chain, 'del-flows' to remove a chain
518 :param cookie: cookie for the installed flowrules (can be used later as identifier for a set of installed chains)
519 :param match: custom match entry to be added to the flowrules (default: only in_port and vlan tag)
520 :param priority: custom flowrule priority
stevenvanrossembf1754e2016-11-17 10:20:52 +0100521 :param monitor: boolean to indicate whether this chain is a monitoring chain
522 :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 +0100523 :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 +0100524 :param path: custom path between the two VNFs (list of switches)
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200525 :return: output log string
526 """
stevenvanrossembecc7c52016-11-07 05:52:01 +0100527
528 # special procedure for monitoring flows
529 if kwargs.get('monitor'):
530
531 # check if chain already exists
532 found_chains = [chain_dict for chain_dict in self.installed_chains if
533 (chain_dict['vnf_src_name'] == vnf_src_name and chain_dict['vnf_src_interface'] == vnf_src_interface
534 and chain_dict['vnf_dst_name'] == vnf_dst_name and chain_dict['vnf_dst_interface'] == vnf_dst_interface)]
535
536 if len(found_chains) > 0:
537 # this chain exists, so need an extra monitoring flow
538 # assume only 1 chain per vnf/interface pair
539 LOG.debug('*** installing monitoring chain on top of pre-defined chain from {0}:{1} -> {2}:{3}'.
540 format(vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface))
541 tag = found_chains[0]['tag']
542 ret = self._addMonitorFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface,
543 tag=tag, table_id=0, **kwargs)
544 return ret
545 else:
546 # no chain existing (or E-LAN) -> install normal chain
stevenvanrossem4fac2af2016-12-22 01:26:02 +0100547 LOG.warning('*** installing monitoring chain without pre-defined NSD chain from {0}:{1} -> {2}:{3}'.
stevenvanrossembecc7c52016-11-07 05:52:01 +0100548 format(vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface))
549 pass
550
551
stevenvanrossem461941c2016-05-10 11:41:29 +0200552 cmd = kwargs.get('cmd')
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100553 if cmd == 'add-flow' or cmd == 'del-flows':
stevenvanrossem461941c2016-05-10 11:41:29 +0200554 ret = self._chainAddFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface, **kwargs)
555 if kwargs.get('bidirectional'):
jokac304ad32017-01-09 10:58:23 +0100556 if kwargs.get('path') is not None:
557 kwargs['path'] = list(reversed(kwargs.get('path')))
stevenvanrossem81955a52016-05-12 14:34:12 +0200558 ret = ret +'\n' + self._chainAddFlow(vnf_dst_name, vnf_src_name, vnf_dst_interface, vnf_src_interface, **kwargs)
stevenvanrossem9315da42016-04-11 12:10:06 +0200559
stevenvanrossem461941c2016-05-10 11:41:29 +0200560 else:
stevenvanrossem81955a52016-05-12 14:34:12 +0200561 ret = "Command unknown"
562
563 return ret
stevenvanrossem461941c2016-05-10 11:41:29 +0200564
565
566 def _chainAddFlow(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
567
peusterm53d3c142016-07-18 10:10:11 +0200568 src_sw = None
peusterm53d3c142016-07-18 10:10:11 +0200569 src_sw_inport_nr = 0
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100570 src_sw_inport_name = None
571 dst_sw = None
peusterm53d3c142016-07-18 10:10:11 +0200572 dst_sw_outport_nr = 0
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100573 dst_sw_outport_name = None
peusterm53d3c142016-07-18 10:10:11 +0200574
575 LOG.debug("call chainAddFlow vnf_src_name=%r, vnf_src_interface=%r, vnf_dst_name=%r, vnf_dst_interface=%r",
576 vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface)
577
stevenvanrossem9315da42016-04-11 12:10:06 +0200578 #check if port is specified (vnf:port)
stevenvanrossemed711fd2016-04-11 16:59:29 +0200579 if vnf_src_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200580 # take first interface by default
581 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
582 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200583 vnf_src_interface = link_dict[0]['src_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200584
stevenvanrossem9315da42016-04-11 12:10:06 +0200585 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
586 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
587 for link in link_dict:
peusterm53d3c142016-07-18 10:10:11 +0200588 if (link_dict[link]['src_port_id'] == vnf_src_interface or
589 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 +0200590 # found the right link and connected switch
stevenvanrossem9315da42016-04-11 12:10:06 +0200591 src_sw = connected_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200592 src_sw_inport_nr = link_dict[link]['dst_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100593 src_sw_inport_name = link_dict[link]['dst_port_name']
stevenvanrossem9315da42016-04-11 12:10:06 +0200594 break
595
stevenvanrossemed711fd2016-04-11 16:59:29 +0200596 if vnf_dst_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200597 # take first interface by default
598 connected_sw = self.DCNetwork_graph.neighbors(vnf_dst_name)[0]
599 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200600 vnf_dst_interface = link_dict[0]['dst_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200601
602 vnf_dst_name = vnf_dst_name.split(':')[0]
603 for connected_sw in self.DCNetwork_graph.neighbors(vnf_dst_name):
604 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
605 for link in link_dict:
peusterm53d3c142016-07-18 10:10:11 +0200606 if link_dict[link]['dst_port_id'] == vnf_dst_interface or \
607 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 +0200608 # found the right link and connected switch
609 dst_sw = connected_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200610 dst_sw_outport_nr = link_dict[link]['src_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100611 dst_sw_outport_name = link_dict[link]['src_port_name']
stevenvanrossem9315da42016-04-11 12:10:06 +0200612 break
613
jokac304ad32017-01-09 10:58:23 +0100614 path = kwargs.get('path')
615 if path is None:
616 # get shortest path
617 try:
618 # returns the first found shortest path
619 # if all shortest paths are wanted, use: all_shortest_paths
620 path = nx.shortest_path(self.DCNetwork_graph, src_sw, dst_sw, weight=kwargs.get('weight'))
621 except:
622 LOG.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
623 vnf_src_name, vnf_dst_name, src_sw, dst_sw))
624 LOG.debug("Graph nodes: %r" % self.DCNetwork_graph.nodes())
625 LOG.debug("Graph edges: %r" % self.DCNetwork_graph.edges())
626 for e, v in self.DCNetwork_graph.edges():
627 LOG.debug("%r" % self.DCNetwork_graph[e][v])
628 return "No path could be found between {0} and {1}".format(vnf_src_name, vnf_dst_name)
stevenvanrossem9315da42016-04-11 12:10:06 +0200629
peustermf9a817d2016-07-18 09:06:04 +0200630 LOG.info("Path between {0} and {1}: {2}".format(vnf_src_name, vnf_dst_name, path))
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100631
stevenvanrossem9315da42016-04-11 12:10:06 +0200632 current_hop = src_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200633 switch_inport_nr = src_sw_inport_nr
stevenvanrossem9315da42016-04-11 12:10:06 +0200634
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100635 # choose free vlan
stevenvanrossem27b6d952016-05-10 16:37:57 +0200636 cmd = kwargs.get('cmd')
637 vlan = None
stevenvanrossem263eee52017-02-08 01:04:36 +0100638 if cmd == 'add-flow':
stevenvanrossembecc7c52016-11-07 05:52:01 +0100639 if kwargs.get('tag'):
640 # use pre-defined tag
641 vlan = kwargs.get('tag')
642 else:
643 vlan = self.vlans.pop()
stevenvanrossem461941c2016-05-10 11:41:29 +0200644
stevenvanrossembecc7c52016-11-07 05:52:01 +0100645 # store the used vlan tag to identify this chain
646 if not kwargs.get('monitor'):
647 chain_dict = {}
648 chain_dict['vnf_src_name'] = vnf_src_name
649 chain_dict['vnf_dst_name'] = vnf_dst_name
650 chain_dict['vnf_src_interface'] = vnf_src_interface
651 chain_dict['vnf_dst_interface'] = vnf_dst_interface
652 chain_dict['tag'] = vlan
653 self.installed_chains.append(chain_dict)
654
655 #iterate through the path to install the flow-entries
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100656 for i in range(0,len(path)):
stevenvanrossem9315da42016-04-11 12:10:06 +0200657 current_node = self.getNodeByName(current_hop)
stevenvanrossem461941c2016-05-10 11:41:29 +0200658
jokac304ad32017-01-09 10:58:23 +0100659 if i < len(path) - 1:
660 next_hop = path[i + 1]
stevenvanrossem9315da42016-04-11 12:10:06 +0200661 else:
jokac304ad32017-01-09 10:58:23 +0100662 # last switch reached
stevenvanrossem9315da42016-04-11 12:10:06 +0200663 next_hop = vnf_dst_name
664
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100665 next_node = self.getNodeByName(next_hop)
666
667 if next_hop == vnf_dst_name:
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200668 switch_outport_nr = dst_sw_outport_nr
peustermf9a817d2016-07-18 09:06:04 +0200669 LOG.info("end node reached: {0}".format(vnf_dst_name))
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100670 elif not isinstance( next_node, OVSSwitch ):
peustermf9a817d2016-07-18 09:06:04 +0200671 LOG.info("Next node: {0} is not a switch".format(next_hop))
stevenvanrossemeefea6c2016-02-17 12:03:26 +0100672 return "Next node: {0} is not a switch".format(next_hop)
stevenvanrossem9315da42016-04-11 12:10:06 +0200673 else:
stevenvanrossemed711fd2016-04-11 16:59:29 +0200674 # take first link between switches by default
stevenvanrossem9315da42016-04-11 12:10:06 +0200675 index_edge_out = 0
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200676 switch_outport_nr = self.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
stevenvanrossem9315da42016-04-11 12:10:06 +0200677
stevenvanrossem9315da42016-04-11 12:10:06 +0200678
stevenvanrossem634c5ef2017-02-08 00:46:02 +0100679 # set OpenFlow entry
stevenvanrossem9315da42016-04-11 12:10:06 +0200680 if isinstance( current_node, OVSSwitch ):
stevenvanrossem461941c2016-05-10 11:41:29 +0200681 kwargs['vlan'] = vlan
682 kwargs['path'] = path
683 kwargs['current_hop'] = current_hop
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100684 kwargs['switch_inport_name'] = src_sw_inport_name
685 kwargs['switch_outport_name'] = dst_sw_outport_name
jokac304ad32017-01-09 10:58:23 +0100686 kwargs['pathindex'] = i
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200687
688 if self.controller == RemoteController:
689 ## set flow entry via ryu rest api
690 self._set_flow_entry_ryu_rest(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
691 else:
692 ## set flow entry via ovs-ofctl
693 self._set_flow_entry_dpctl(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
694
stevenvanrossemed711fd2016-04-11 16:59:29 +0200695 # take first link between switches by default
696 if isinstance( next_node, OVSSwitch ):
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200697 switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
stevenvanrossemed711fd2016-04-11 16:59:29 +0200698 current_hop = next_hop
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100699
stevenvanrossem27b6d952016-05-10 16:37:57 +0200700 return "path {2} between {0} and {1}".format(vnf_src_name, vnf_dst_name, cmd)
701
702 def _set_flow_entry_ryu_rest(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
703 match = 'in_port=%s' % switch_inport_nr
704
705 cookie = kwargs.get('cookie')
706 match_input = kwargs.get('match')
707 cmd = kwargs.get('cmd')
708 path = kwargs.get('path')
jokac304ad32017-01-09 10:58:23 +0100709 index = kwargs.get('pathindex')
710
stevenvanrossem27b6d952016-05-10 16:37:57 +0200711 vlan = kwargs.get('vlan')
stevenvanrossem61699eb2016-08-05 15:57:59 +0200712 priority = kwargs.get('priority')
stevenvanrossembecc7c52016-11-07 05:52:01 +0100713 # flag to not set the ovs port vlan tag
714 skip_vlan_tag = kwargs.get('skip_vlan_tag')
715 # table id to put this flowentry
716 table_id = kwargs.get('table_id')
717 if not table_id:
718 table_id = 0
stevenvanrossem27b6d952016-05-10 16:37:57 +0200719
720 s = ','
721 if match_input:
722 match = s.join([match, match_input])
723
724 flow = {}
725 flow['dpid'] = int(node.dpid, 16)
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200726
stevenvanrossem27b6d952016-05-10 16:37:57 +0200727 if cookie:
728 flow['cookie'] = int(cookie)
stevenvanrossem61699eb2016-08-05 15:57:59 +0200729 if priority:
730 flow['priority'] = int(priority)
stevenvanrossem27b6d952016-05-10 16:37:57 +0200731
stevenvanrossembecc7c52016-11-07 05:52:01 +0100732 flow['table_id'] = table_id
733
stevenvanrossem27b6d952016-05-10 16:37:57 +0200734 flow['actions'] = []
735
736 # possible Ryu actions, match fields:
737 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#add-a-flow-entry
738 if cmd == 'add-flow':
739 prefix = 'stats/flowentry/add'
stevenvanrossem27b6d952016-05-10 16:37:57 +0200740 if vlan != None:
jokac304ad32017-01-09 10:58:23 +0100741 if index == 0: # first node
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100742 # set vlan tag in ovs instance (to isolate E-LANs)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100743 if not skip_vlan_tag:
744 in_port_name = kwargs.get('switch_inport_name')
745 self._set_vlan_tag(node, in_port_name, vlan)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100746 # set vlan push action if more than 1 switch in the path
747 if len(path) > 1:
748 action = {}
749 action['type'] = 'PUSH_VLAN' # Push a new VLAN tag if a input frame is non-VLAN-tagged
750 action['ethertype'] = 33024 # Ethertype 0x8100(=33024): IEEE 802.1Q VLAN-tagged frame
751 flow['actions'].append(action)
752 action = {}
753 action['type'] = 'SET_FIELD'
754 action['field'] = 'vlan_vid'
stevenvanrossem9cc73602017-01-27 23:37:29 +0100755 # ryu expects the field to be masked
756 action['value'] = vlan | 0x1000
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100757 flow['actions'].append(action)
758
stevenvanrossem9cc73602017-01-27 23:37:29 +0100759 elif index == len(path) - 1: # last node
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100760 # set vlan tag in ovs instance (to isolate E-LANs)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100761 if not skip_vlan_tag:
762 out_port_name = kwargs.get('switch_outport_name')
763 self._set_vlan_tag(node, out_port_name, vlan)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100764 # set vlan pop action if more than 1 switch in the path
765 if len(path) > 1:
766 match += ',dl_vlan=%s' % vlan
767 action = {}
768 action['type'] = 'POP_VLAN'
769 flow['actions'].append(action)
770
stevenvanrossem27b6d952016-05-10 16:37:57 +0200771 else: # middle nodes
stevenvanrossem27b6d952016-05-10 16:37:57 +0200772 match += ',dl_vlan=%s' % vlan
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100773
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200774 # output action must come last
775 action = {}
776 action['type'] = 'OUTPUT'
777 action['port'] = switch_outport_nr
778 flow['actions'].append(action)
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200779
stevenvanrossem27b6d952016-05-10 16:37:57 +0200780 elif cmd == 'del-flows':
stevenvanrossem27b6d952016-05-10 16:37:57 +0200781 prefix = 'stats/flowentry/delete'
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200782
stevenvanrossem27b6d952016-05-10 16:37:57 +0200783 if cookie:
stevenvanrossem1ef77022016-05-12 16:36:10 +0200784 # TODO: add cookie_mask as argument
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200785 flow['cookie_mask'] = int('0xffffffffffffffff', 16) # need full mask to match complete cookie
stevenvanrossem27b6d952016-05-10 16:37:57 +0200786
787 action = {}
788 action['type'] = 'OUTPUT'
789 action['port'] = switch_outport_nr
790 flow['actions'].append(action)
791
792 flow['match'] = self._parse_match(match)
793 self.ryu_REST(prefix, data=flow)
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100794
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100795 def _set_vlan_tag(self, node, switch_port, tag):
796 node.vsctl('set', 'port {0} tag={1}'.format(switch_port,tag))
797 LOG.debug("set vlan in switch: {0} in_port: {1} vlan tag: {2}".format(node.name, switch_port, tag))
798
stevenvanrossem461941c2016-05-10 11:41:29 +0200799 def _set_flow_entry_dpctl(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100800
stevenvanrossem23c48092016-05-06 17:21:12 +0200801 match = 'in_port=%s' % switch_inport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200802
803 cookie = kwargs.get('cookie')
804 match_input = kwargs.get('match')
805 cmd = kwargs.get('cmd')
806 path = kwargs.get('path')
jokac304ad32017-01-09 10:58:23 +0100807 index = kwargs.get('pathindex')
stevenvanrossem461941c2016-05-10 11:41:29 +0200808 vlan = kwargs.get('vlan')
809
stevenvanrossem898a2af2016-05-06 18:28:57 +0200810 s = ','
811 if cookie:
812 cookie = 'cookie=%s' % cookie
813 match = s.join([cookie, match])
stevenvanrossem23c48092016-05-06 17:21:12 +0200814 if match_input:
stevenvanrossem23c48092016-05-06 17:21:12 +0200815 match = s.join([match, match_input])
stevenvanrossem23c48092016-05-06 17:21:12 +0200816 if cmd == 'add-flow':
817 action = 'action=%s' % switch_outport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200818 if vlan != None:
jokac304ad32017-01-09 10:58:23 +0100819 if index == 0: # first node
stevenvanrossem461941c2016-05-10 11:41:29 +0200820 action = ('action=mod_vlan_vid:%s' % vlan) + (',output=%s' % switch_outport_nr)
821 match = '-O OpenFlow13 ' + match
jokac304ad32017-01-09 10:58:23 +0100822 elif index == len(path) - 1: # last node
stevenvanrossem461941c2016-05-10 11:41:29 +0200823 match += ',dl_vlan=%s' % vlan
824 action = 'action=strip_vlan,output=%s' % switch_outport_nr
825 else: # middle nodes
826 match += ',dl_vlan=%s' % vlan
stevenvanrossem23c48092016-05-06 17:21:12 +0200827 ofcmd = s.join([match, action])
828 elif cmd == 'del-flows':
829 ofcmd = match
830 else:
831 ofcmd = ''
832
833 node.dpctl(cmd, ofcmd)
peustermf9a817d2016-07-18 09:06:04 +0200834 LOG.info("{3} in switch: {0} in_port: {1} out_port: {2}".format(node.name, switch_inport_nr,
stevenvanrossem461941c2016-05-10 11:41:29 +0200835 switch_outport_nr, cmd))
stevenvanrossem23c48092016-05-06 17:21:12 +0200836
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100837 # start Ryu Openflow controller as Remote Controller for the DCNetwork
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200838 def startRyu(self, learning_switch=True):
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100839 # start Ryu controller with rest-API
840 python_install_path = site.getsitepackages()[0]
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100841 # ryu default learning switch
842 #ryu_path = python_install_path + '/ryu/app/simple_switch_13.py'
843 #custom learning switch that installs a default NORMAL action in the ovs switches
844 dir_path = os.path.dirname(os.path.realpath(__file__))
845 ryu_path = dir_path + '/son_emu_simple_switch_13.py'
peustermde14f332016-03-15 16:14:21 +0100846 ryu_path2 = python_install_path + '/ryu/app/ofctl_rest.py'
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100847 # change the default Openflow controller port to 6653 (official IANA-assigned port number), as used by Mininet
848 # Ryu still uses 6633 as default
849 ryu_option = '--ofp-tcp-listen-port'
850 ryu_of_port = '6653'
peustermde14f332016-03-15 16:14:21 +0100851 ryu_cmd = 'ryu-manager'
peustermef6629e2016-03-14 17:21:56 +0100852 FNULL = open("/tmp/ryu.log", 'w')
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200853 if learning_switch:
854 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 +0100855 LOG.debug('starting ryu-controller with {0}'.format(ryu_path))
856 LOG.debug('starting ryu-controller with {0}'.format(ryu_path2))
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200857 else:
stevenvanrossem73efd192016-06-29 01:44:07 +0200858 # no learning switch, but with rest api
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200859 self.ryu_process = Popen([ryu_cmd, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100860 LOG.debug('starting ryu-controller with {0}'.format(ryu_path2))
peusterm391773a2016-03-14 17:40:43 +0100861 time.sleep(1)
862
peusterm8b04b532016-07-19 16:55:38 +0200863 def killRyu(self):
864 """
865 Stop the Ryu controller that might be started by son-emu.
866 :return:
867 """
868 # try it nicely
peustermde14f332016-03-15 16:14:21 +0100869 if self.ryu_process is not None:
peusterm391773a2016-03-14 17:40:43 +0100870 self.ryu_process.terminate()
871 self.ryu_process.kill()
peusterm8b04b532016-07-19 16:55:38 +0200872 # ensure its death ;-)
stevenvanrossem89706802016-07-19 02:54:45 +0200873 Popen(['pkill', '-f', 'ryu-manager'])
peusterm391773a2016-03-14 17:40:43 +0100874
stevenvanrossem27b6d952016-05-10 16:37:57 +0200875 def ryu_REST(self, prefix, dpid=None, data=None):
stevenvanrossem27b6d952016-05-10 16:37:57 +0200876
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200877 if dpid:
878 url = self.ryu_REST_api + '/' + str(prefix) + '/' + str(dpid)
879 else:
880 url = self.ryu_REST_api + '/' + str(prefix)
881 if data:
882 req = self.RyuSession.post(url, json=data)
883 else:
884 req = self.RyuSession.get(url)
885
886
887 # do extra logging if status code is not 200 (OK)
888 if req.status_code is not requests.codes.ok:
889 logging.info(
890 'type {0} encoding: {1} text: {2} headers: {3} history: {4}'.format(req.headers['content-type'],
891 req.encoding, req.text,
892 req.headers, req.history))
893 LOG.info('url: {0}'.format(str(url)))
894 if data: LOG.info('POST: {0}'.format(str(data)))
895 LOG.info('status: {0} reason: {1}'.format(req.status_code, req.reason))
896
897
898 if 'json' in req.headers['content-type']:
899 ret = req.json()
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200900 return ret
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200901
902 ret = req.text.rstrip()
903 return ret
904
stevenvanrossem27b6d952016-05-10 16:37:57 +0200905
906 # need to respect that some match fields must be integers
907 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#description-of-match-and-actions
908 def _parse_match(self, match):
909 matches = match.split(',')
910 dict = {}
911 for m in matches:
912 match = m.split('=')
913 if len(match) == 2:
914 try:
915 m2 = int(match[1], 0)
916 except:
917 m2 = match[1]
918
919 dict.update({match[0]:m2})
920 return dict
921
stevenvanrossem17b6e882017-05-04 16:51:34 +0200922 def find_connected_dc_interface(self, vnf_src_name, vnf_src_interface=None):
923
924 if vnf_src_interface is None:
925 # take first interface by default
926 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
927 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
928 vnf_src_interface = link_dict[0]['src_port_id']
929
stevenvanrossem566779d2016-11-07 06:33:44 +0100930 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
931 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
932 for link in link_dict:
933 if (link_dict[link]['src_port_id'] == vnf_src_interface or
934 link_dict[link]['src_port_name'] == vnf_src_interface): # Fix: we might also get interface names, e.g, from a son-emu-cli call
935 # found the right link and connected switch
936 src_sw = connected_sw
937 src_sw_inport_nr = link_dict[link]['dst_port_nr']
938 src_sw_inport_name = link_dict[link]['dst_port_name']
stevenvanrossem7062cee2016-12-22 10:31:38 +0100939 return src_sw_inport_name