blob: 5dfcbcdfc964f58f0530fe0ccebad2738040e9ea [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 """
peusterm92a37cd2017-05-17 09:51:05 +0200236 if link is not None:
237 node1 = link.intf1.node
238 node2 = link.intf2.node
239 assert node1 is not None
240 assert node2 is not None
stevenvanrossem00e65b92017-04-18 16:57:40 +0200241 Containernet.removeLink(self, link=link, node1=node1, node2=node2)
peusterm92a37cd2017-05-17 09:51:05 +0200242 # TODO we might decrease the loglevel to debug:
243 try:
244 self.DCNetwork_graph.remove_edge(node2.name, node1.name)
245 except:
246 LOG.warning("%s not found in DCNetwork_graph." % ((node2.name, node1.name)))
247 try:
248 self.DCNetwork_graph.remove_edge(node1.name, node2.name)
249 except:
250 LOG.warning("%s not found in DCNetwork_graph." % ((node1.name, node2.name)))
stevenvanrossem00e65b92017-04-18 16:57:40 +0200251
peusterma47db032016-02-04 14:55:29 +0100252 def addDocker( self, label, **params ):
peusterm5b844a12016-01-11 15:58:15 +0100253 """
peusterm293cbc32016-01-13 17:05:28 +0100254 Wrapper for addDocker method to use custom container class.
peusterm5b844a12016-01-11 15:58:15 +0100255 """
stevenvanrossemf3712012017-05-04 00:01:52 +0200256 self.DCNetwork_graph.add_node(label, type=params.get('type', 'docker'))
peusterm5877ea22016-05-11 13:44:59 +0200257 return Containernet.addDocker(self, label, cls=EmulatorCompute, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100258
stevenvanrossemf3712012017-05-04 00:01:52 +0200259 def removeDocker( self, label, **params):
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100260 """
261 Wrapper for removeDocker method to update graph.
262 """
stevenvanrossem8fbf9782016-02-17 11:40:23 +0100263 self.DCNetwork_graph.remove_node(label)
peusterm5877ea22016-05-11 13:44:59 +0200264 return Containernet.removeDocker(self, label, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100265
stevenvanrossemf3712012017-05-04 00:01:52 +0200266 def addExtSAP(self, sap_name, sap_ip, **params):
267 """
268 Wrapper for addExtSAP method to store SAP also in graph.
269 """
270 # make sure that 'type' is set
271 params['type'] = params.get('type','sap_ext')
272 self.DCNetwork_graph.add_node(sap_name, type=params['type'])
stevenvanrossemf3712012017-05-04 00:01:52 +0200273 return Containernet.addExtSAP(self, sap_name, sap_ip, **params)
274
275 def removeExtSAP(self, sap_name, **params):
276 """
277 Wrapper for removeExtSAP method to remove SAP also from graph.
278 """
279 self.DCNetwork_graph.remove_node(sap_name)
280 return Containernet.removeExtSAP(self, sap_name)
281
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100282 def addSwitch( self, name, add_to_graph=True, **params ):
283 """
284 Wrapper for addSwitch method to store switch also in graph.
285 """
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100286
287 # add this switch to the global topology overview
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100288 if add_to_graph:
stevenvanrossemf3712012017-05-04 00:01:52 +0200289 self.DCNetwork_graph.add_node(name, type=params.get('type','switch'))
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100290
291 # set the learning switch behavior
292 if 'failMode' in params :
293 failMode = params['failMode']
294 else :
295 failMode = self.failMode
296
297 s = Containernet.addSwitch(self, name, protocols='OpenFlow10,OpenFlow12,OpenFlow13', failMode=failMode, **params)
298
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100299 return s
peustermc3b977e2016-01-12 10:09:35 +0100300
peustermbd44f4a2016-01-13 14:53:30 +0100301 def getAllContainers(self):
302 """
303 Returns a list with all containers within all data centers.
304 """
305 all_containers = []
306 for dc in self.dcs.itervalues():
307 all_containers += dc.listCompute()
308 return all_containers
309
peustermcbcd4c22015-12-28 11:33:42 +0100310 def start(self):
311 # start
312 for dc in self.dcs.itervalues():
313 dc.start()
peusterm5877ea22016-05-11 13:44:59 +0200314 Containernet.start(self)
peustermcbcd4c22015-12-28 11:33:42 +0100315
316 def stop(self):
stevenvanrossem60670da2016-04-15 15:31:28 +0200317
stevenvanrossemc6abf132016-04-14 11:15:58 +0200318 # stop the monitor agent
stevenvanrossem60670da2016-04-15 15:31:28 +0200319 if self.monitor_agent is not None:
320 self.monitor_agent.stop()
peustermcbcd4c22015-12-28 11:33:42 +0100321
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200322 # stop emulator net
peusterm5877ea22016-05-11 13:44:59 +0200323 Containernet.stop(self)
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200324
325 # stop Ryu controller
peusterm8b04b532016-07-19 16:55:38 +0200326 self.killRyu()
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200327
328
peustermcbcd4c22015-12-28 11:33:42 +0100329 def CLI(self):
peusterm293cbc32016-01-13 17:05:28 +0100330 CLI(self)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100331
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100332 def setLAN(self, vnf_list):
333 """
334 setup an E-LAN network by assigning the same VLAN tag to each DC interface of the VNFs in the E-LAN
335
336 :param vnf_list: names of the VNFs in this E-LAN [{name:,interface:},...]
337 :return:
338 """
339 src_sw = None
340 src_sw_inport_nr = 0
341 src_sw_inport_name = None
342
343 # get a vlan tag for this E-LAN
344 vlan = self.vlans.pop()
345
346 for vnf in vnf_list:
347 vnf_src_name = vnf['name']
348 vnf_src_interface = vnf['interface']
349
350 # check if port is specified (vnf:port)
351 if vnf_src_interface is None:
352 # take first interface by default
353 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
354 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
355 vnf_src_interface = link_dict[0]['src_port_id']
356
357 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
358 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
359 for link in link_dict:
360 if (link_dict[link]['src_port_id'] == vnf_src_interface or
361 link_dict[link]['src_port_name'] == vnf_src_interface): # Fix: we might also get interface names, e.g, from a son-emu-cli call
362 # found the right link and connected switch
363 src_sw = connected_sw
364 src_sw_inport_nr = link_dict[link]['dst_port_nr']
365 src_sw_inport_name = link_dict[link]['dst_port_name']
366 break
367
368 # set the tag on the dc switch interface
369 LOG.debug('set E-LAN: vnf name: {0} interface: {1} tag: {2}'.format(vnf_src_name, vnf_src_interface,vlan))
370 switch_node = self.getNodeByName(src_sw)
371 self._set_vlan_tag(switch_node, src_sw_inport_name, vlan)
372
stevenvanrossembecc7c52016-11-07 05:52:01 +0100373 def _addMonitorFlow(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None,
374 tag=None, **kwargs):
stevenvanrossembf1754e2016-11-17 10:20:52 +0100375 """
376 Add a monitoring flow entry that adds a special flowentry/counter at the begin or end of a chain.
377 So this monitoring flowrule exists on top of a previously defined chain rule and uses the same vlan tag/routing.
378 :param vnf_src_name:
379 :param vnf_dst_name:
380 :param vnf_src_interface:
381 :param vnf_dst_interface:
382 :param tag: vlan tag to be used for this chain (same tag as existing chain)
383 :param monitor_placement: 'tx' or 'rx' indicating to place the extra flowentry resp. at the beginning or end of the chain
384 :return:
385 """
stevenvanrossembecc7c52016-11-07 05:52:01 +0100386
387 src_sw = None
388 src_sw_inport_nr = 0
389 src_sw_inport_name = None
390 dst_sw = None
391 dst_sw_outport_nr = 0
392 dst_sw_outport_name = None
393
394 LOG.debug("call AddMonitorFlow vnf_src_name=%r, vnf_src_interface=%r, vnf_dst_name=%r, vnf_dst_interface=%r",
395 vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface)
396
397 #check if port is specified (vnf:port)
398 if vnf_src_interface is None:
399 # take first interface by default
400 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
401 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
402 vnf_src_interface = link_dict[0]['src_port_id']
403
404 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
405 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
406 for link in link_dict:
407 if (link_dict[link]['src_port_id'] == vnf_src_interface or
408 link_dict[link]['src_port_name'] == vnf_src_interface): # Fix: we might also get interface names, e.g, from a son-emu-cli call
409 # found the right link and connected switch
410 src_sw = connected_sw
411 src_sw_inport_nr = link_dict[link]['dst_port_nr']
412 src_sw_inport_name = link_dict[link]['dst_port_name']
413 break
414
415 if vnf_dst_interface is None:
416 # take first interface by default
417 connected_sw = self.DCNetwork_graph.neighbors(vnf_dst_name)[0]
418 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
419 vnf_dst_interface = link_dict[0]['dst_port_id']
420
421 vnf_dst_name = vnf_dst_name.split(':')[0]
422 for connected_sw in self.DCNetwork_graph.neighbors(vnf_dst_name):
423 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
424 for link in link_dict:
425 if link_dict[link]['dst_port_id'] == vnf_dst_interface or \
426 link_dict[link]['dst_port_name'] == vnf_dst_interface: # Fix: we might also get interface names, e.g, from a son-emu-cli call
427 # found the right link and connected switch
428 dst_sw = connected_sw
429 dst_sw_outport_nr = link_dict[link]['src_port_nr']
430 dst_sw_outport_name = link_dict[link]['src_port_name']
431 break
432
433 if not tag >= 0:
434 LOG.exception('tag not valid: {0}'.format(tag))
435
436 # get shortest path
437 try:
438 # returns the first found shortest path
439 # if all shortest paths are wanted, use: all_shortest_paths
440 path = nx.shortest_path(self.DCNetwork_graph, src_sw, dst_sw, weight=kwargs.get('weight'))
441 except:
442 LOG.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
443 vnf_src_name, vnf_dst_name, src_sw, dst_sw))
444 LOG.debug("Graph nodes: %r" % self.DCNetwork_graph.nodes())
445 LOG.debug("Graph edges: %r" % self.DCNetwork_graph.edges())
446 for e, v in self.DCNetwork_graph.edges():
447 LOG.debug("%r" % self.DCNetwork_graph[e][v])
448 return "No path could be found between {0} and {1}".format(vnf_src_name, vnf_dst_name)
449
450 LOG.info("Path between {0} and {1}: {2}".format(vnf_src_name, vnf_dst_name, path))
451
452 current_hop = src_sw
453 switch_inport_nr = src_sw_inport_nr
454
455 cmd = kwargs.get('cmd')
456
457 #iterate through the path to install the flow-entries
458 for i in range(0,len(path)):
459 current_node = self.getNodeByName(current_hop)
460
461 if path.index(current_hop) < len(path)-1:
462 next_hop = path[path.index(current_hop)+1]
463 else:
464 #last switch reached
465 next_hop = vnf_dst_name
466
467 next_node = self.getNodeByName(next_hop)
468
469 if next_hop == vnf_dst_name:
470 switch_outport_nr = dst_sw_outport_nr
471 LOG.info("end node reached: {0}".format(vnf_dst_name))
472 elif not isinstance( next_node, OVSSwitch ):
473 LOG.info("Next node: {0} is not a switch".format(next_hop))
474 return "Next node: {0} is not a switch".format(next_hop)
475 else:
476 # take first link between switches by default
477 index_edge_out = 0
478 switch_outport_nr = self.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100479
480
stevenvanrossembecc7c52016-11-07 05:52:01 +0100481 # set of entry via ovs-ofctl
482 if isinstance( current_node, OVSSwitch ):
483 kwargs['vlan'] = tag
484 kwargs['path'] = path
485 kwargs['current_hop'] = current_hop
486 kwargs['switch_inport_name'] = src_sw_inport_name
487 kwargs['switch_outport_name'] = dst_sw_outport_name
488 kwargs['skip_vlan_tag'] = True
stevenvanrossem263eee52017-02-08 01:04:36 +0100489 kwargs['pathindex'] = i
stevenvanrossembecc7c52016-11-07 05:52:01 +0100490
stevenvanrossem4fac2af2016-12-22 01:26:02 +0100491 monitor_placement = kwargs.get('monitor_placement').strip()
stevenvanrossembecc7c52016-11-07 05:52:01 +0100492 # put monitor flow at the dst switch
493 insert_flow = False
494 if monitor_placement == 'tx' and path.index(current_hop) == 0: # first node:
495 insert_flow = True
496 # put monitoring flow at the src switch
497 elif monitor_placement == 'rx' and path.index(current_hop) == len(path) - 1: # last node:
498 insert_flow = True
stevenvanrossem4fac2af2016-12-22 01:26:02 +0100499 elif monitor_placement not in ['rx', 'tx']:
stevenvanrossembecc7c52016-11-07 05:52:01 +0100500 LOG.exception('invalid monitor command: {0}'.format(monitor_placement))
501
502
503 if self.controller == RemoteController and insert_flow:
504 ## set flow entry via ryu rest api
505 self._set_flow_entry_ryu_rest(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
506 break
507 elif insert_flow:
508 ## set flow entry via ovs-ofctl
509 self._set_flow_entry_dpctl(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
510 break
511
512 # take first link between switches by default
513 if isinstance( next_node, OVSSwitch ):
514 switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
515 current_hop = next_hop
516
517 return "path {2} between {0} and {1}".format(vnf_src_name, vnf_dst_name, cmd)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100518
519
stevenvanrossem461941c2016-05-10 11:41:29 +0200520 def setChain(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200521 """
522 Chain 2 vnf interfaces together by installing the flowrules in the switches along their path.
523 Currently the path is found using the default networkx shortest path function.
524 Each chain gets a unique vlan id , so different chains wil not interfere.
525
526 :param vnf_src_name: vnf name (string)
527 :param vnf_dst_name: vnf name (string)
528 :param vnf_src_interface: source interface name (string)
529 :param vnf_dst_interface: destination interface name (string)
530 :param cmd: 'add-flow' (default) to add a chain, 'del-flows' to remove a chain
531 :param cookie: cookie for the installed flowrules (can be used later as identifier for a set of installed chains)
532 :param match: custom match entry to be added to the flowrules (default: only in_port and vlan tag)
533 :param priority: custom flowrule priority
stevenvanrossembf1754e2016-11-17 10:20:52 +0100534 :param monitor: boolean to indicate whether this chain is a monitoring chain
535 :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 +0100536 :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 +0100537 :param path: custom path between the two VNFs (list of switches)
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200538 :return: output log string
539 """
stevenvanrossembecc7c52016-11-07 05:52:01 +0100540
541 # special procedure for monitoring flows
542 if kwargs.get('monitor'):
543
544 # check if chain already exists
545 found_chains = [chain_dict for chain_dict in self.installed_chains if
546 (chain_dict['vnf_src_name'] == vnf_src_name and chain_dict['vnf_src_interface'] == vnf_src_interface
547 and chain_dict['vnf_dst_name'] == vnf_dst_name and chain_dict['vnf_dst_interface'] == vnf_dst_interface)]
548
549 if len(found_chains) > 0:
550 # this chain exists, so need an extra monitoring flow
551 # assume only 1 chain per vnf/interface pair
552 LOG.debug('*** installing monitoring chain on top of pre-defined chain from {0}:{1} -> {2}:{3}'.
553 format(vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface))
554 tag = found_chains[0]['tag']
555 ret = self._addMonitorFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface,
556 tag=tag, table_id=0, **kwargs)
557 return ret
558 else:
559 # no chain existing (or E-LAN) -> install normal chain
stevenvanrossem4fac2af2016-12-22 01:26:02 +0100560 LOG.warning('*** installing monitoring chain without pre-defined NSD chain from {0}:{1} -> {2}:{3}'.
stevenvanrossembecc7c52016-11-07 05:52:01 +0100561 format(vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface))
562 pass
563
564
stevenvanrossem461941c2016-05-10 11:41:29 +0200565 cmd = kwargs.get('cmd')
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100566 if cmd == 'add-flow' or cmd == 'del-flows':
stevenvanrossem461941c2016-05-10 11:41:29 +0200567 ret = self._chainAddFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface, **kwargs)
568 if kwargs.get('bidirectional'):
jokac304ad32017-01-09 10:58:23 +0100569 if kwargs.get('path') is not None:
570 kwargs['path'] = list(reversed(kwargs.get('path')))
stevenvanrossem81955a52016-05-12 14:34:12 +0200571 ret = ret +'\n' + self._chainAddFlow(vnf_dst_name, vnf_src_name, vnf_dst_interface, vnf_src_interface, **kwargs)
stevenvanrossem9315da42016-04-11 12:10:06 +0200572
stevenvanrossem461941c2016-05-10 11:41:29 +0200573 else:
stevenvanrossem81955a52016-05-12 14:34:12 +0200574 ret = "Command unknown"
575
576 return ret
stevenvanrossem461941c2016-05-10 11:41:29 +0200577
578
579 def _chainAddFlow(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
580
peusterm53d3c142016-07-18 10:10:11 +0200581 src_sw = None
peusterm53d3c142016-07-18 10:10:11 +0200582 src_sw_inport_nr = 0
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100583 src_sw_inport_name = None
584 dst_sw = None
peusterm53d3c142016-07-18 10:10:11 +0200585 dst_sw_outport_nr = 0
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100586 dst_sw_outport_name = None
peusterm53d3c142016-07-18 10:10:11 +0200587
588 LOG.debug("call chainAddFlow vnf_src_name=%r, vnf_src_interface=%r, vnf_dst_name=%r, vnf_dst_interface=%r",
589 vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface)
590
stevenvanrossem9315da42016-04-11 12:10:06 +0200591 #check if port is specified (vnf:port)
stevenvanrossemed711fd2016-04-11 16:59:29 +0200592 if vnf_src_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200593 # take first interface by default
594 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
595 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200596 vnf_src_interface = link_dict[0]['src_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200597
stevenvanrossem9315da42016-04-11 12:10:06 +0200598 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
599 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
600 for link in link_dict:
peusterm53d3c142016-07-18 10:10:11 +0200601 if (link_dict[link]['src_port_id'] == vnf_src_interface or
602 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 +0200603 # found the right link and connected switch
stevenvanrossem9315da42016-04-11 12:10:06 +0200604 src_sw = connected_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200605 src_sw_inport_nr = link_dict[link]['dst_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100606 src_sw_inport_name = link_dict[link]['dst_port_name']
stevenvanrossem9315da42016-04-11 12:10:06 +0200607 break
608
stevenvanrossemed711fd2016-04-11 16:59:29 +0200609 if vnf_dst_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200610 # take first interface by default
611 connected_sw = self.DCNetwork_graph.neighbors(vnf_dst_name)[0]
612 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200613 vnf_dst_interface = link_dict[0]['dst_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200614
615 vnf_dst_name = vnf_dst_name.split(':')[0]
616 for connected_sw in self.DCNetwork_graph.neighbors(vnf_dst_name):
617 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
618 for link in link_dict:
peusterm53d3c142016-07-18 10:10:11 +0200619 if link_dict[link]['dst_port_id'] == vnf_dst_interface or \
620 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 +0200621 # found the right link and connected switch
622 dst_sw = connected_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200623 dst_sw_outport_nr = link_dict[link]['src_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100624 dst_sw_outport_name = link_dict[link]['src_port_name']
stevenvanrossem9315da42016-04-11 12:10:06 +0200625 break
626
jokac304ad32017-01-09 10:58:23 +0100627 path = kwargs.get('path')
628 if path is None:
629 # get shortest path
630 try:
631 # returns the first found shortest path
632 # if all shortest paths are wanted, use: all_shortest_paths
633 path = nx.shortest_path(self.DCNetwork_graph, src_sw, dst_sw, weight=kwargs.get('weight'))
634 except:
635 LOG.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
636 vnf_src_name, vnf_dst_name, src_sw, dst_sw))
637 LOG.debug("Graph nodes: %r" % self.DCNetwork_graph.nodes())
638 LOG.debug("Graph edges: %r" % self.DCNetwork_graph.edges())
639 for e, v in self.DCNetwork_graph.edges():
640 LOG.debug("%r" % self.DCNetwork_graph[e][v])
641 return "No path could be found between {0} and {1}".format(vnf_src_name, vnf_dst_name)
stevenvanrossem9315da42016-04-11 12:10:06 +0200642
peustermf9a817d2016-07-18 09:06:04 +0200643 LOG.info("Path between {0} and {1}: {2}".format(vnf_src_name, vnf_dst_name, path))
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100644
stevenvanrossem9315da42016-04-11 12:10:06 +0200645 current_hop = src_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200646 switch_inport_nr = src_sw_inport_nr
stevenvanrossem9315da42016-04-11 12:10:06 +0200647
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100648 # choose free vlan
stevenvanrossem27b6d952016-05-10 16:37:57 +0200649 cmd = kwargs.get('cmd')
650 vlan = None
stevenvanrossem263eee52017-02-08 01:04:36 +0100651 if cmd == 'add-flow':
stevenvanrossembecc7c52016-11-07 05:52:01 +0100652 if kwargs.get('tag'):
653 # use pre-defined tag
654 vlan = kwargs.get('tag')
655 else:
656 vlan = self.vlans.pop()
stevenvanrossem461941c2016-05-10 11:41:29 +0200657
stevenvanrossembecc7c52016-11-07 05:52:01 +0100658 # store the used vlan tag to identify this chain
659 if not kwargs.get('monitor'):
660 chain_dict = {}
661 chain_dict['vnf_src_name'] = vnf_src_name
662 chain_dict['vnf_dst_name'] = vnf_dst_name
663 chain_dict['vnf_src_interface'] = vnf_src_interface
664 chain_dict['vnf_dst_interface'] = vnf_dst_interface
665 chain_dict['tag'] = vlan
666 self.installed_chains.append(chain_dict)
667
668 #iterate through the path to install the flow-entries
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100669 for i in range(0,len(path)):
stevenvanrossem9315da42016-04-11 12:10:06 +0200670 current_node = self.getNodeByName(current_hop)
stevenvanrossem461941c2016-05-10 11:41:29 +0200671
jokac304ad32017-01-09 10:58:23 +0100672 if i < len(path) - 1:
673 next_hop = path[i + 1]
stevenvanrossem9315da42016-04-11 12:10:06 +0200674 else:
jokac304ad32017-01-09 10:58:23 +0100675 # last switch reached
stevenvanrossem9315da42016-04-11 12:10:06 +0200676 next_hop = vnf_dst_name
677
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100678 next_node = self.getNodeByName(next_hop)
679
680 if next_hop == vnf_dst_name:
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200681 switch_outport_nr = dst_sw_outport_nr
peustermf9a817d2016-07-18 09:06:04 +0200682 LOG.info("end node reached: {0}".format(vnf_dst_name))
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100683 elif not isinstance( next_node, OVSSwitch ):
peustermf9a817d2016-07-18 09:06:04 +0200684 LOG.info("Next node: {0} is not a switch".format(next_hop))
stevenvanrossemeefea6c2016-02-17 12:03:26 +0100685 return "Next node: {0} is not a switch".format(next_hop)
stevenvanrossem9315da42016-04-11 12:10:06 +0200686 else:
stevenvanrossemed711fd2016-04-11 16:59:29 +0200687 # take first link between switches by default
stevenvanrossem9315da42016-04-11 12:10:06 +0200688 index_edge_out = 0
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200689 switch_outport_nr = self.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
stevenvanrossem9315da42016-04-11 12:10:06 +0200690
stevenvanrossem9315da42016-04-11 12:10:06 +0200691
stevenvanrossem634c5ef2017-02-08 00:46:02 +0100692 # set OpenFlow entry
stevenvanrossem9315da42016-04-11 12:10:06 +0200693 if isinstance( current_node, OVSSwitch ):
stevenvanrossem461941c2016-05-10 11:41:29 +0200694 kwargs['vlan'] = vlan
695 kwargs['path'] = path
696 kwargs['current_hop'] = current_hop
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100697 kwargs['switch_inport_name'] = src_sw_inport_name
698 kwargs['switch_outport_name'] = dst_sw_outport_name
jokac304ad32017-01-09 10:58:23 +0100699 kwargs['pathindex'] = i
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200700
701 if self.controller == RemoteController:
702 ## set flow entry via ryu rest api
703 self._set_flow_entry_ryu_rest(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
704 else:
705 ## set flow entry via ovs-ofctl
706 self._set_flow_entry_dpctl(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
707
stevenvanrossemed711fd2016-04-11 16:59:29 +0200708 # take first link between switches by default
709 if isinstance( next_node, OVSSwitch ):
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200710 switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
stevenvanrossemed711fd2016-04-11 16:59:29 +0200711 current_hop = next_hop
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100712
stevenvanrossem27b6d952016-05-10 16:37:57 +0200713 return "path {2} between {0} and {1}".format(vnf_src_name, vnf_dst_name, cmd)
714
715 def _set_flow_entry_ryu_rest(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
716 match = 'in_port=%s' % switch_inport_nr
717
718 cookie = kwargs.get('cookie')
719 match_input = kwargs.get('match')
720 cmd = kwargs.get('cmd')
721 path = kwargs.get('path')
jokac304ad32017-01-09 10:58:23 +0100722 index = kwargs.get('pathindex')
723
stevenvanrossem27b6d952016-05-10 16:37:57 +0200724 vlan = kwargs.get('vlan')
stevenvanrossem61699eb2016-08-05 15:57:59 +0200725 priority = kwargs.get('priority')
stevenvanrossembecc7c52016-11-07 05:52:01 +0100726 # flag to not set the ovs port vlan tag
727 skip_vlan_tag = kwargs.get('skip_vlan_tag')
728 # table id to put this flowentry
729 table_id = kwargs.get('table_id')
730 if not table_id:
731 table_id = 0
stevenvanrossem27b6d952016-05-10 16:37:57 +0200732
733 s = ','
734 if match_input:
735 match = s.join([match, match_input])
736
737 flow = {}
738 flow['dpid'] = int(node.dpid, 16)
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200739
stevenvanrossem27b6d952016-05-10 16:37:57 +0200740 if cookie:
741 flow['cookie'] = int(cookie)
stevenvanrossem61699eb2016-08-05 15:57:59 +0200742 if priority:
743 flow['priority'] = int(priority)
stevenvanrossem27b6d952016-05-10 16:37:57 +0200744
stevenvanrossembecc7c52016-11-07 05:52:01 +0100745 flow['table_id'] = table_id
746
stevenvanrossem27b6d952016-05-10 16:37:57 +0200747 flow['actions'] = []
748
749 # possible Ryu actions, match fields:
750 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#add-a-flow-entry
751 if cmd == 'add-flow':
752 prefix = 'stats/flowentry/add'
stevenvanrossem27b6d952016-05-10 16:37:57 +0200753 if vlan != None:
jokac304ad32017-01-09 10:58:23 +0100754 if index == 0: # first node
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100755 # set vlan tag in ovs instance (to isolate E-LANs)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100756 if not skip_vlan_tag:
757 in_port_name = kwargs.get('switch_inport_name')
758 self._set_vlan_tag(node, in_port_name, vlan)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100759 # set vlan push action if more than 1 switch in the path
760 if len(path) > 1:
761 action = {}
762 action['type'] = 'PUSH_VLAN' # Push a new VLAN tag if a input frame is non-VLAN-tagged
763 action['ethertype'] = 33024 # Ethertype 0x8100(=33024): IEEE 802.1Q VLAN-tagged frame
764 flow['actions'].append(action)
765 action = {}
766 action['type'] = 'SET_FIELD'
767 action['field'] = 'vlan_vid'
stevenvanrossem9cc73602017-01-27 23:37:29 +0100768 # ryu expects the field to be masked
769 action['value'] = vlan | 0x1000
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100770 flow['actions'].append(action)
771
stevenvanrossem9cc73602017-01-27 23:37:29 +0100772 elif index == len(path) - 1: # last node
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100773 # set vlan tag in ovs instance (to isolate E-LANs)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100774 if not skip_vlan_tag:
775 out_port_name = kwargs.get('switch_outport_name')
776 self._set_vlan_tag(node, out_port_name, vlan)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100777 # set vlan pop action if more than 1 switch in the path
778 if len(path) > 1:
779 match += ',dl_vlan=%s' % vlan
780 action = {}
781 action['type'] = 'POP_VLAN'
782 flow['actions'].append(action)
783
stevenvanrossem27b6d952016-05-10 16:37:57 +0200784 else: # middle nodes
stevenvanrossem27b6d952016-05-10 16:37:57 +0200785 match += ',dl_vlan=%s' % vlan
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100786
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200787 # output action must come last
788 action = {}
789 action['type'] = 'OUTPUT'
790 action['port'] = switch_outport_nr
791 flow['actions'].append(action)
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200792
stevenvanrossem27b6d952016-05-10 16:37:57 +0200793 elif cmd == 'del-flows':
stevenvanrossem27b6d952016-05-10 16:37:57 +0200794 prefix = 'stats/flowentry/delete'
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200795
stevenvanrossem27b6d952016-05-10 16:37:57 +0200796 if cookie:
stevenvanrossem1ef77022016-05-12 16:36:10 +0200797 # TODO: add cookie_mask as argument
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200798 flow['cookie_mask'] = int('0xffffffffffffffff', 16) # need full mask to match complete cookie
stevenvanrossem27b6d952016-05-10 16:37:57 +0200799
800 action = {}
801 action['type'] = 'OUTPUT'
802 action['port'] = switch_outport_nr
803 flow['actions'].append(action)
804
805 flow['match'] = self._parse_match(match)
806 self.ryu_REST(prefix, data=flow)
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100807
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100808 def _set_vlan_tag(self, node, switch_port, tag):
809 node.vsctl('set', 'port {0} tag={1}'.format(switch_port,tag))
810 LOG.debug("set vlan in switch: {0} in_port: {1} vlan tag: {2}".format(node.name, switch_port, tag))
811
stevenvanrossem461941c2016-05-10 11:41:29 +0200812 def _set_flow_entry_dpctl(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100813
stevenvanrossem23c48092016-05-06 17:21:12 +0200814 match = 'in_port=%s' % switch_inport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200815
816 cookie = kwargs.get('cookie')
817 match_input = kwargs.get('match')
818 cmd = kwargs.get('cmd')
819 path = kwargs.get('path')
jokac304ad32017-01-09 10:58:23 +0100820 index = kwargs.get('pathindex')
stevenvanrossem461941c2016-05-10 11:41:29 +0200821 vlan = kwargs.get('vlan')
822
stevenvanrossem898a2af2016-05-06 18:28:57 +0200823 s = ','
824 if cookie:
825 cookie = 'cookie=%s' % cookie
826 match = s.join([cookie, match])
stevenvanrossem23c48092016-05-06 17:21:12 +0200827 if match_input:
stevenvanrossem23c48092016-05-06 17:21:12 +0200828 match = s.join([match, match_input])
stevenvanrossem23c48092016-05-06 17:21:12 +0200829 if cmd == 'add-flow':
830 action = 'action=%s' % switch_outport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200831 if vlan != None:
jokac304ad32017-01-09 10:58:23 +0100832 if index == 0: # first node
stevenvanrossem461941c2016-05-10 11:41:29 +0200833 action = ('action=mod_vlan_vid:%s' % vlan) + (',output=%s' % switch_outport_nr)
834 match = '-O OpenFlow13 ' + match
jokac304ad32017-01-09 10:58:23 +0100835 elif index == len(path) - 1: # last node
stevenvanrossem461941c2016-05-10 11:41:29 +0200836 match += ',dl_vlan=%s' % vlan
837 action = 'action=strip_vlan,output=%s' % switch_outport_nr
838 else: # middle nodes
839 match += ',dl_vlan=%s' % vlan
stevenvanrossem23c48092016-05-06 17:21:12 +0200840 ofcmd = s.join([match, action])
841 elif cmd == 'del-flows':
842 ofcmd = match
843 else:
844 ofcmd = ''
845
846 node.dpctl(cmd, ofcmd)
peustermf9a817d2016-07-18 09:06:04 +0200847 LOG.info("{3} in switch: {0} in_port: {1} out_port: {2}".format(node.name, switch_inport_nr,
stevenvanrossem461941c2016-05-10 11:41:29 +0200848 switch_outport_nr, cmd))
stevenvanrossem23c48092016-05-06 17:21:12 +0200849
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100850 # start Ryu Openflow controller as Remote Controller for the DCNetwork
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200851 def startRyu(self, learning_switch=True):
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100852 # start Ryu controller with rest-API
853 python_install_path = site.getsitepackages()[0]
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100854 # ryu default learning switch
855 #ryu_path = python_install_path + '/ryu/app/simple_switch_13.py'
856 #custom learning switch that installs a default NORMAL action in the ovs switches
857 dir_path = os.path.dirname(os.path.realpath(__file__))
858 ryu_path = dir_path + '/son_emu_simple_switch_13.py'
peustermde14f332016-03-15 16:14:21 +0100859 ryu_path2 = python_install_path + '/ryu/app/ofctl_rest.py'
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100860 # change the default Openflow controller port to 6653 (official IANA-assigned port number), as used by Mininet
861 # Ryu still uses 6633 as default
862 ryu_option = '--ofp-tcp-listen-port'
863 ryu_of_port = '6653'
peustermde14f332016-03-15 16:14:21 +0100864 ryu_cmd = 'ryu-manager'
peustermef6629e2016-03-14 17:21:56 +0100865 FNULL = open("/tmp/ryu.log", 'w')
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200866 if learning_switch:
867 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 +0100868 LOG.debug('starting ryu-controller with {0}'.format(ryu_path))
869 LOG.debug('starting ryu-controller with {0}'.format(ryu_path2))
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200870 else:
stevenvanrossem73efd192016-06-29 01:44:07 +0200871 # no learning switch, but with rest api
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200872 self.ryu_process = Popen([ryu_cmd, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100873 LOG.debug('starting ryu-controller with {0}'.format(ryu_path2))
peusterm391773a2016-03-14 17:40:43 +0100874 time.sleep(1)
875
peusterm8b04b532016-07-19 16:55:38 +0200876 def killRyu(self):
877 """
878 Stop the Ryu controller that might be started by son-emu.
879 :return:
880 """
881 # try it nicely
peustermde14f332016-03-15 16:14:21 +0100882 if self.ryu_process is not None:
peusterm391773a2016-03-14 17:40:43 +0100883 self.ryu_process.terminate()
884 self.ryu_process.kill()
peusterm8b04b532016-07-19 16:55:38 +0200885 # ensure its death ;-)
stevenvanrossem89706802016-07-19 02:54:45 +0200886 Popen(['pkill', '-f', 'ryu-manager'])
peusterm391773a2016-03-14 17:40:43 +0100887
stevenvanrossem27b6d952016-05-10 16:37:57 +0200888 def ryu_REST(self, prefix, dpid=None, data=None):
stevenvanrossem27b6d952016-05-10 16:37:57 +0200889
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200890 if dpid:
891 url = self.ryu_REST_api + '/' + str(prefix) + '/' + str(dpid)
892 else:
893 url = self.ryu_REST_api + '/' + str(prefix)
894 if data:
895 req = self.RyuSession.post(url, json=data)
896 else:
897 req = self.RyuSession.get(url)
898
899
900 # do extra logging if status code is not 200 (OK)
901 if req.status_code is not requests.codes.ok:
902 logging.info(
903 'type {0} encoding: {1} text: {2} headers: {3} history: {4}'.format(req.headers['content-type'],
904 req.encoding, req.text,
905 req.headers, req.history))
906 LOG.info('url: {0}'.format(str(url)))
907 if data: LOG.info('POST: {0}'.format(str(data)))
908 LOG.info('status: {0} reason: {1}'.format(req.status_code, req.reason))
909
910
911 if 'json' in req.headers['content-type']:
912 ret = req.json()
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200913 return ret
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200914
915 ret = req.text.rstrip()
916 return ret
917
stevenvanrossem27b6d952016-05-10 16:37:57 +0200918
919 # need to respect that some match fields must be integers
920 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#description-of-match-and-actions
921 def _parse_match(self, match):
922 matches = match.split(',')
923 dict = {}
924 for m in matches:
925 match = m.split('=')
926 if len(match) == 2:
927 try:
928 m2 = int(match[1], 0)
929 except:
930 m2 = match[1]
931
932 dict.update({match[0]:m2})
933 return dict
934
stevenvanrossem17b6e882017-05-04 16:51:34 +0200935 def find_connected_dc_interface(self, vnf_src_name, vnf_src_interface=None):
936
937 if vnf_src_interface is None:
938 # take first interface by default
939 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
940 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
941 vnf_src_interface = link_dict[0]['src_port_id']
942
stevenvanrossem566779d2016-11-07 06:33:44 +0100943 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
944 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
945 for link in link_dict:
946 if (link_dict[link]['src_port_id'] == vnf_src_interface or
947 link_dict[link]['src_port_name'] == vnf_src_interface): # Fix: we might also get interface names, e.g, from a son-emu-cli call
948 # found the right link and connected switch
949 src_sw = connected_sw
950 src_sw_inport_nr = link_dict[link]['dst_port_nr']
951 src_sw_inport_name = link_dict[link]['dst_port_name']
stevenvanrossem7062cee2016-12-22 10:31:38 +0100952 return src_sw_inport_name