blob: 531bdab5d4d56f86a07cddb500890b28cd642acf [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
stevenvanrossemae588012017-06-06 10:33:19 +020036import json
stevenvanrossem9ebd0942016-02-22 10:13:05 +010037
peusterm5877ea22016-05-11 13:44:59 +020038from mininet.net import Containernet
peustermef6629e2016-03-14 17:21:56 +010039from mininet.node import Controller, DefaultController, OVSSwitch, OVSKernelSwitch, Docker, RemoteController
peustermcbcd4c22015-12-28 11:33:42 +010040from mininet.cli import CLI
peustermea8db832016-03-08 10:25:58 +010041from mininet.link import TCLink
peusterm8b04b532016-07-19 16:55:38 +020042from mininet.clean import cleanup
stevenvanrossemc5a536a2016-02-16 14:52:39 +010043import networkx as nx
cgeoffroy9524ad32016-03-03 18:24:15 +010044from emuvim.dcemulator.monitoring import DCNetworkMonitor
stevenvanrossem17b6e882017-05-04 16:51:34 +020045from emuvim.dcemulator.node import Datacenter, EmulatorCompute, EmulatorExtSAP
peusterm42f08be2016-03-10 21:56:34 +010046from emuvim.dcemulator.resourcemodel import ResourceModelRegistrar
peustermcbcd4c22015-12-28 11:33:42 +010047
peustermf9a817d2016-07-18 09:06:04 +020048LOG = logging.getLogger("dcemulator.net")
49LOG.setLevel(logging.DEBUG)
50
stevenvanrossemb3f34172016-11-16 23:30:57 +010051# default CPU period used for cpu percentage-based cfs values (microseconds)
52CPU_PERIOD = 1000000
53
stevenvanrossemae588012017-06-06 10:33:19 +020054# default priority setting for added flow-rules
55DEFAULT_PRIORITY = 1000
56# default cookie number for new flow-rules
57DEFAULT_COOKIE = 10
58
peusterm5877ea22016-05-11 13:44:59 +020059class DCNetwork(Containernet):
peusterme4e89d32016-01-07 09:14:54 +010060 """
peusterm5877ea22016-05-11 13:44:59 +020061 Wraps the original Mininet/Containernet class and provides
peusterme4e89d32016-01-07 09:14:54 +010062 methods to add data centers, switches, etc.
63
64 This class is used by topology definition scripts.
65 """
peustermcbcd4c22015-12-28 11:33:42 +010066
peusterm0ec25102016-04-16 02:16:20 +020067 def __init__(self, controller=RemoteController, monitor=False,
stevenvanrossemb3f34172016-11-16 23:30:57 +010068 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 +010069 dc_emulation_max_cpu=1.0, # fraction of overall CPU time for emulation
70 dc_emulation_max_mem=512, # emulation max mem in MB
71 **kwargs):
peusterm42f08be2016-03-10 21:56:34 +010072 """
peusterm5877ea22016-05-11 13:44:59 +020073 Create an extended version of a Containernet network
peusterm42f08be2016-03-10 21:56:34 +010074 :param dc_emulation_max_cpu: max. CPU time used by containers in data centers
75 :param kwargs: path through for Mininet parameters
76 :return:
77 """
peusterm8b04b532016-07-19 16:55:38 +020078 # members
peustermcbcd4c22015-12-28 11:33:42 +010079 self.dcs = {}
peusterm8b04b532016-07-19 16:55:38 +020080 self.ryu_process = None
stevenvanrossembecc7c52016-11-07 05:52:01 +010081 #list of deployed nsds.E_Lines and E_LANs (uploaded from the dummy gatekeeper)
82 self.deployed_nsds = []
83 self.deployed_elines = []
84 self.deployed_elans = []
85 self.installed_chains = []
86
peusterm42f08be2016-03-10 21:56:34 +010087
peusterm8b04b532016-07-19 16:55:38 +020088 # always cleanup environment before we start the emulator
stevenvanrossem89706802016-07-19 02:54:45 +020089 self.killRyu()
peusterm8b04b532016-07-19 16:55:38 +020090 cleanup()
stevenvanrossem89706802016-07-19 02:54:45 +020091
peusterm293cbc32016-01-13 17:05:28 +010092 # call original Docker.__init__ and setup default controller
peusterm5877ea22016-05-11 13:44:59 +020093 Containernet.__init__(
stevenvanrossem7cd3c252016-05-11 22:55:15 +020094 self, switch=OVSKernelSwitch, controller=controller, **kwargs)
stevenvanrossemc5a536a2016-02-16 14:52:39 +010095
stevenvanrossemc3a344f2016-11-04 19:34:47 +010096 # default switch configuration
97 enable_ryu_learning = False
98 if enable_learning :
99 self.failMode = 'standalone'
100 enable_ryu_learning = True
101 else:
102 self.failMode = 'secure'
103
peustermde14f332016-03-15 16:14:21 +0100104 # Ryu management
peustermde14f332016-03-15 16:14:21 +0100105 if controller == RemoteController:
106 # start Ryu controller
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100107 self.startRyu(learning_switch=enable_ryu_learning)
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100108
peustermde14f332016-03-15 16:14:21 +0100109 # add the specified controller
peustermef6629e2016-03-14 17:21:56 +0100110 self.addController('c0', controller=controller)
111
112 # graph of the complete DC network
stevenvanrossemc1149022016-04-11 01:16:44 +0200113 self.DCNetwork_graph = nx.MultiDiGraph()
peustermef6629e2016-03-14 17:21:56 +0100114
stevenvanrossem461941c2016-05-10 11:41:29 +0200115 # initialize pool of vlan tags to setup the SDN paths
116 self.vlans = range(4096)[::-1]
117
stevenvanrossem27b6d952016-05-10 16:37:57 +0200118 # link to Ryu REST_API
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200119 ryu_ip = 'localhost'
stevenvanrossem27b6d952016-05-10 16:37:57 +0200120 ryu_port = '8080'
121 self.ryu_REST_api = 'http://{0}:{1}'.format(ryu_ip, ryu_port)
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200122 self.RyuSession = requests.Session()
stevenvanrossem27b6d952016-05-10 16:37:57 +0200123
peustermef6629e2016-03-14 17:21:56 +0100124 # monitoring agent
stevenvanrossem60670da2016-04-15 15:31:28 +0200125 if monitor:
126 self.monitor_agent = DCNetworkMonitor(self)
127 else:
128 self.monitor_agent = None
peustermef6629e2016-03-14 17:21:56 +0100129
peusterm42f08be2016-03-10 21:56:34 +0100130 # initialize resource model registrar
peusterma4d84792016-03-25 12:27:07 +0100131 self.rm_registrar = ResourceModelRegistrar(
132 dc_emulation_max_cpu, dc_emulation_max_mem)
stevenvanrossemb3f34172016-11-16 23:30:57 +0100133 self.cpu_period = CPU_PERIOD
peustermcbcd4c22015-12-28 11:33:42 +0100134
peusterm60bf8b82016-04-06 14:12:35 +0200135 def addDatacenter(self, label, metadata={}, resource_log_path=None):
peustermcbcd4c22015-12-28 11:33:42 +0100136 """
137 Create and add a logical cloud data center to the network.
138 """
peusterma47db032016-02-04 14:55:29 +0100139 if label in self.dcs:
140 raise Exception("Data center label already exists: %s" % label)
peusterm60bf8b82016-04-06 14:12:35 +0200141 dc = Datacenter(label, metadata=metadata, resource_log_path=resource_log_path)
peustermcbcd4c22015-12-28 11:33:42 +0100142 dc.net = self # set reference to network
peusterma47db032016-02-04 14:55:29 +0100143 self.dcs[label] = dc
peustermcbcd4c22015-12-28 11:33:42 +0100144 dc.create() # finally create the data center in our Mininet instance
peustermf9a817d2016-07-18 09:06:04 +0200145 LOG.info("added data center: %s" % label)
peustermcbcd4c22015-12-28 11:33:42 +0100146 return dc
147
peusterme6092692016-01-11 16:32:58 +0100148 def addLink(self, node1, node2, **params):
peusterm5b844a12016-01-11 15:58:15 +0100149 """
150 Able to handle Datacenter objects as link
151 end points.
152 """
peustermcbcd4c22015-12-28 11:33:42 +0100153 assert node1 is not None
154 assert node2 is not None
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100155
peustermcbcd4c22015-12-28 11:33:42 +0100156 # ensure type of node1
157 if isinstance( node1, basestring ):
158 if node1 in self.dcs:
159 node1 = self.dcs[node1].switch
peustermcbcd4c22015-12-28 11:33:42 +0100160 if isinstance( node1, Datacenter ):
161 node1 = node1.switch
162 # ensure type of node2
163 if isinstance( node2, basestring ):
164 if node2 in self.dcs:
165 node2 = self.dcs[node2].switch
peustermcbcd4c22015-12-28 11:33:42 +0100166 if isinstance( node2, Datacenter ):
167 node2 = node2.switch
peustermc3b977e2016-01-12 10:09:35 +0100168 # try to give containers a default IP
169 if isinstance( node1, Docker ):
peustermea8db832016-03-08 10:25:58 +0100170 if "params1" not in params:
peustermc3b977e2016-01-12 10:09:35 +0100171 params["params1"] = {}
peustermea8db832016-03-08 10:25:58 +0100172 if "ip" not in params["params1"]:
peustermc3b977e2016-01-12 10:09:35 +0100173 params["params1"]["ip"] = self.getNextIp()
174 if isinstance( node2, Docker ):
peustermea8db832016-03-08 10:25:58 +0100175 if "params2" not in params:
peustermc3b977e2016-01-12 10:09:35 +0100176 params["params2"] = {}
peustermea8db832016-03-08 10:25:58 +0100177 if "ip" not in params["params2"]:
peustermc3b977e2016-01-12 10:09:35 +0100178 params["params2"]["ip"] = self.getNextIp()
peustermea8db832016-03-08 10:25:58 +0100179 # ensure that we allow TCLinks between data centers
180 # TODO this is not optimal, we use cls=Link for containers and TCLink for data centers
peusterm5877ea22016-05-11 13:44:59 +0200181 # see Containernet issue: https://github.com/mpeuster/containernet/issues/3
peustermea8db832016-03-08 10:25:58 +0100182 if "cls" not in params:
183 params["cls"] = TCLink
peustermc3b977e2016-01-12 10:09:35 +0100184
peusterm5877ea22016-05-11 13:44:59 +0200185 link = Containernet.addLink(self, node1, node2, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100186
stevenvanrossemc1149022016-04-11 01:16:44 +0200187 # try to give container interfaces a default id
188 node1_port_id = node1.ports[link.intf1]
189 if isinstance(node1, Docker):
190 if "id" in params["params1"]:
191 node1_port_id = params["params1"]["id"]
stevenvanrossem5b376412016-05-04 15:34:49 +0200192 node1_port_name = link.intf1.name
stevenvanrossemc1149022016-04-11 01:16:44 +0200193
194 node2_port_id = node2.ports[link.intf2]
195 if isinstance(node2, Docker):
196 if "id" in params["params2"]:
197 node2_port_id = params["params2"]["id"]
stevenvanrossem5b376412016-05-04 15:34:49 +0200198 node2_port_name = link.intf2.name
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200199
200
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100201 # add edge and assigned port number to graph in both directions between node1 and node2
stevenvanrossemc1149022016-04-11 01:16:44 +0200202 # port_id: id given in descriptor (if available, otherwise same as port)
peusterm5877ea22016-05-11 13:44:59 +0200203 # port: portnumber assigned by Containernet
stevenvanrossemc1149022016-04-11 01:16:44 +0200204
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200205 attr_dict = {}
206 # possible weight metrics allowed by TClink class:
207 weight_metrics = ['bw', 'delay', 'jitter', 'loss']
208 edge_attributes = [p for p in params if p in weight_metrics]
209 for attr in edge_attributes:
210 # if delay: strip ms (need number as weight in graph)
joka27edb4f2017-01-17 12:40:59 +0100211 match = re.search('([0-9]*\.?[0-9]+)', str(params[attr]))
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200212 if match:
213 attr_number = match.group(1)
214 else:
215 attr_number = None
216 attr_dict[attr] = attr_number
217
218
stevenvanrossem5b376412016-05-04 15:34:49 +0200219 attr_dict2 = {'src_port_id': node1_port_id, 'src_port_nr': node1.ports[link.intf1],
220 'src_port_name': node1_port_name,
221 'dst_port_id': node2_port_id, 'dst_port_nr': node2.ports[link.intf2],
222 'dst_port_name': node2_port_name}
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200223 attr_dict2.update(attr_dict)
224 self.DCNetwork_graph.add_edge(node1.name, node2.name, attr_dict=attr_dict2)
225
stevenvanrossem5b376412016-05-04 15:34:49 +0200226 attr_dict2 = {'src_port_id': node2_port_id, 'src_port_nr': node2.ports[link.intf2],
227 'src_port_name': node2_port_name,
228 'dst_port_id': node1_port_id, 'dst_port_nr': node1.ports[link.intf1],
229 'dst_port_name': node1_port_name}
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200230 attr_dict2.update(attr_dict)
231 self.DCNetwork_graph.add_edge(node2.name, node1.name, attr_dict=attr_dict2)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100232
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100233 LOG.debug("addLink: n1={0} intf1={1} -- n2={2} intf2={3}".format(
234 str(node1),node1_port_name, str(node2), node2_port_name))
235
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100236 return link
peustermcbcd4c22015-12-28 11:33:42 +0100237
stevenvanrossem00e65b92017-04-18 16:57:40 +0200238 def removeLink(self, link=None, node1=None, node2=None):
239 """
240 Remove the link from the Containernet and the networkx graph
241 """
peusterm92a37cd2017-05-17 09:51:05 +0200242 if link is not None:
243 node1 = link.intf1.node
244 node2 = link.intf2.node
245 assert node1 is not None
246 assert node2 is not None
stevenvanrossem00e65b92017-04-18 16:57:40 +0200247 Containernet.removeLink(self, link=link, node1=node1, node2=node2)
peusterm92a37cd2017-05-17 09:51:05 +0200248 # TODO we might decrease the loglevel to debug:
249 try:
250 self.DCNetwork_graph.remove_edge(node2.name, node1.name)
251 except:
peustermbf6b25f2017-06-26 14:08:51 +0200252 LOG.warning("%s, %s not found in DCNetwork_graph." % ((node2.name, node1.name)))
peusterm92a37cd2017-05-17 09:51:05 +0200253 try:
254 self.DCNetwork_graph.remove_edge(node1.name, node2.name)
255 except:
peustermbf6b25f2017-06-26 14:08:51 +0200256 LOG.warning("%s, %s not found in DCNetwork_graph." % ((node1.name, node2.name)))
stevenvanrossem00e65b92017-04-18 16:57:40 +0200257
peusterma47db032016-02-04 14:55:29 +0100258 def addDocker( self, label, **params ):
peusterm5b844a12016-01-11 15:58:15 +0100259 """
peusterm293cbc32016-01-13 17:05:28 +0100260 Wrapper for addDocker method to use custom container class.
peusterm5b844a12016-01-11 15:58:15 +0100261 """
stevenvanrossemf3712012017-05-04 00:01:52 +0200262 self.DCNetwork_graph.add_node(label, type=params.get('type', 'docker'))
peusterm5877ea22016-05-11 13:44:59 +0200263 return Containernet.addDocker(self, label, cls=EmulatorCompute, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100264
stevenvanrossemf3712012017-05-04 00:01:52 +0200265 def removeDocker( self, label, **params):
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100266 """
267 Wrapper for removeDocker method to update graph.
268 """
stevenvanrossem8fbf9782016-02-17 11:40:23 +0100269 self.DCNetwork_graph.remove_node(label)
peusterm5877ea22016-05-11 13:44:59 +0200270 return Containernet.removeDocker(self, label, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100271
stevenvanrossemf3712012017-05-04 00:01:52 +0200272 def addExtSAP(self, sap_name, sap_ip, **params):
273 """
274 Wrapper for addExtSAP method to store SAP also in graph.
275 """
276 # make sure that 'type' is set
277 params['type'] = params.get('type','sap_ext')
278 self.DCNetwork_graph.add_node(sap_name, type=params['type'])
stevenvanrossemf3712012017-05-04 00:01:52 +0200279 return Containernet.addExtSAP(self, sap_name, sap_ip, **params)
280
281 def removeExtSAP(self, sap_name, **params):
282 """
283 Wrapper for removeExtSAP method to remove SAP also from graph.
284 """
285 self.DCNetwork_graph.remove_node(sap_name)
286 return Containernet.removeExtSAP(self, sap_name)
287
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100288 def addSwitch( self, name, add_to_graph=True, **params ):
289 """
290 Wrapper for addSwitch method to store switch also in graph.
291 """
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100292
293 # add this switch to the global topology overview
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100294 if add_to_graph:
stevenvanrossemf3712012017-05-04 00:01:52 +0200295 self.DCNetwork_graph.add_node(name, type=params.get('type','switch'))
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100296
297 # set the learning switch behavior
298 if 'failMode' in params :
299 failMode = params['failMode']
300 else :
301 failMode = self.failMode
302
303 s = Containernet.addSwitch(self, name, protocols='OpenFlow10,OpenFlow12,OpenFlow13', failMode=failMode, **params)
304
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100305 return s
peustermc3b977e2016-01-12 10:09:35 +0100306
peustermbd44f4a2016-01-13 14:53:30 +0100307 def getAllContainers(self):
308 """
309 Returns a list with all containers within all data centers.
310 """
311 all_containers = []
312 for dc in self.dcs.itervalues():
313 all_containers += dc.listCompute()
314 return all_containers
315
peustermcbcd4c22015-12-28 11:33:42 +0100316 def start(self):
317 # start
318 for dc in self.dcs.itervalues():
319 dc.start()
peusterm5877ea22016-05-11 13:44:59 +0200320 Containernet.start(self)
peustermcbcd4c22015-12-28 11:33:42 +0100321
322 def stop(self):
stevenvanrossem60670da2016-04-15 15:31:28 +0200323
stevenvanrossemc6abf132016-04-14 11:15:58 +0200324 # stop the monitor agent
stevenvanrossem60670da2016-04-15 15:31:28 +0200325 if self.monitor_agent is not None:
326 self.monitor_agent.stop()
peustermcbcd4c22015-12-28 11:33:42 +0100327
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200328 # stop emulator net
peusterm5877ea22016-05-11 13:44:59 +0200329 Containernet.stop(self)
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200330
331 # stop Ryu controller
peusterm8b04b532016-07-19 16:55:38 +0200332 self.killRyu()
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200333
334
peustermcbcd4c22015-12-28 11:33:42 +0100335 def CLI(self):
peusterm293cbc32016-01-13 17:05:28 +0100336 CLI(self)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100337
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100338 def setLAN(self, vnf_list):
339 """
340 setup an E-LAN network by assigning the same VLAN tag to each DC interface of the VNFs in the E-LAN
341
342 :param vnf_list: names of the VNFs in this E-LAN [{name:,interface:},...]
343 :return:
344 """
345 src_sw = None
346 src_sw_inport_nr = 0
347 src_sw_inport_name = None
348
349 # get a vlan tag for this E-LAN
350 vlan = self.vlans.pop()
351
352 for vnf in vnf_list:
353 vnf_src_name = vnf['name']
354 vnf_src_interface = vnf['interface']
355
356 # check if port is specified (vnf:port)
357 if vnf_src_interface is None:
358 # take first interface by default
359 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
360 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
361 vnf_src_interface = link_dict[0]['src_port_id']
362
363 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
364 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
365 for link in link_dict:
366 if (link_dict[link]['src_port_id'] == vnf_src_interface or
367 link_dict[link]['src_port_name'] == vnf_src_interface): # Fix: we might also get interface names, e.g, from a son-emu-cli call
368 # found the right link and connected switch
369 src_sw = connected_sw
370 src_sw_inport_nr = link_dict[link]['dst_port_nr']
371 src_sw_inport_name = link_dict[link]['dst_port_name']
372 break
373
374 # set the tag on the dc switch interface
375 LOG.debug('set E-LAN: vnf name: {0} interface: {1} tag: {2}'.format(vnf_src_name, vnf_src_interface,vlan))
376 switch_node = self.getNodeByName(src_sw)
377 self._set_vlan_tag(switch_node, src_sw_inport_name, vlan)
378
stevenvanrossembecc7c52016-11-07 05:52:01 +0100379 def _addMonitorFlow(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None,
380 tag=None, **kwargs):
stevenvanrossembf1754e2016-11-17 10:20:52 +0100381 """
382 Add a monitoring flow entry that adds a special flowentry/counter at the begin or end of a chain.
383 So this monitoring flowrule exists on top of a previously defined chain rule and uses the same vlan tag/routing.
384 :param vnf_src_name:
385 :param vnf_dst_name:
386 :param vnf_src_interface:
387 :param vnf_dst_interface:
388 :param tag: vlan tag to be used for this chain (same tag as existing chain)
389 :param monitor_placement: 'tx' or 'rx' indicating to place the extra flowentry resp. at the beginning or end of the chain
390 :return:
391 """
stevenvanrossembecc7c52016-11-07 05:52:01 +0100392
393 src_sw = None
394 src_sw_inport_nr = 0
395 src_sw_inport_name = None
396 dst_sw = None
397 dst_sw_outport_nr = 0
398 dst_sw_outport_name = None
399
400 LOG.debug("call AddMonitorFlow vnf_src_name=%r, vnf_src_interface=%r, vnf_dst_name=%r, vnf_dst_interface=%r",
401 vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface)
402
403 #check if port is specified (vnf:port)
404 if vnf_src_interface is None:
405 # take first interface by default
406 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
407 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
408 vnf_src_interface = link_dict[0]['src_port_id']
409
410 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
411 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
412 for link in link_dict:
413 if (link_dict[link]['src_port_id'] == vnf_src_interface or
414 link_dict[link]['src_port_name'] == vnf_src_interface): # Fix: we might also get interface names, e.g, from a son-emu-cli call
415 # found the right link and connected switch
416 src_sw = connected_sw
417 src_sw_inport_nr = link_dict[link]['dst_port_nr']
418 src_sw_inport_name = link_dict[link]['dst_port_name']
419 break
420
421 if vnf_dst_interface is None:
422 # take first interface by default
423 connected_sw = self.DCNetwork_graph.neighbors(vnf_dst_name)[0]
424 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
425 vnf_dst_interface = link_dict[0]['dst_port_id']
426
427 vnf_dst_name = vnf_dst_name.split(':')[0]
428 for connected_sw in self.DCNetwork_graph.neighbors(vnf_dst_name):
429 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
430 for link in link_dict:
431 if link_dict[link]['dst_port_id'] == vnf_dst_interface or \
432 link_dict[link]['dst_port_name'] == vnf_dst_interface: # Fix: we might also get interface names, e.g, from a son-emu-cli call
433 # found the right link and connected switch
434 dst_sw = connected_sw
435 dst_sw_outport_nr = link_dict[link]['src_port_nr']
436 dst_sw_outport_name = link_dict[link]['src_port_name']
437 break
438
439 if not tag >= 0:
440 LOG.exception('tag not valid: {0}'.format(tag))
441
442 # get shortest path
443 try:
444 # returns the first found shortest path
445 # if all shortest paths are wanted, use: all_shortest_paths
446 path = nx.shortest_path(self.DCNetwork_graph, src_sw, dst_sw, weight=kwargs.get('weight'))
447 except:
448 LOG.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
449 vnf_src_name, vnf_dst_name, src_sw, dst_sw))
450 LOG.debug("Graph nodes: %r" % self.DCNetwork_graph.nodes())
451 LOG.debug("Graph edges: %r" % self.DCNetwork_graph.edges())
452 for e, v in self.DCNetwork_graph.edges():
453 LOG.debug("%r" % self.DCNetwork_graph[e][v])
454 return "No path could be found between {0} and {1}".format(vnf_src_name, vnf_dst_name)
455
456 LOG.info("Path between {0} and {1}: {2}".format(vnf_src_name, vnf_dst_name, path))
457
458 current_hop = src_sw
459 switch_inport_nr = src_sw_inport_nr
460
461 cmd = kwargs.get('cmd')
462
463 #iterate through the path to install the flow-entries
464 for i in range(0,len(path)):
465 current_node = self.getNodeByName(current_hop)
466
467 if path.index(current_hop) < len(path)-1:
468 next_hop = path[path.index(current_hop)+1]
469 else:
470 #last switch reached
471 next_hop = vnf_dst_name
472
473 next_node = self.getNodeByName(next_hop)
474
475 if next_hop == vnf_dst_name:
476 switch_outport_nr = dst_sw_outport_nr
477 LOG.info("end node reached: {0}".format(vnf_dst_name))
478 elif not isinstance( next_node, OVSSwitch ):
479 LOG.info("Next node: {0} is not a switch".format(next_hop))
480 return "Next node: {0} is not a switch".format(next_hop)
481 else:
482 # take first link between switches by default
483 index_edge_out = 0
484 switch_outport_nr = self.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100485
486
stevenvanrossembecc7c52016-11-07 05:52:01 +0100487 # set of entry via ovs-ofctl
488 if isinstance( current_node, OVSSwitch ):
489 kwargs['vlan'] = tag
490 kwargs['path'] = path
491 kwargs['current_hop'] = current_hop
492 kwargs['switch_inport_name'] = src_sw_inport_name
493 kwargs['switch_outport_name'] = dst_sw_outport_name
494 kwargs['skip_vlan_tag'] = True
stevenvanrossem263eee52017-02-08 01:04:36 +0100495 kwargs['pathindex'] = i
stevenvanrossembecc7c52016-11-07 05:52:01 +0100496
stevenvanrossem4fac2af2016-12-22 01:26:02 +0100497 monitor_placement = kwargs.get('monitor_placement').strip()
stevenvanrossembecc7c52016-11-07 05:52:01 +0100498 # put monitor flow at the dst switch
499 insert_flow = False
500 if monitor_placement == 'tx' and path.index(current_hop) == 0: # first node:
501 insert_flow = True
502 # put monitoring flow at the src switch
503 elif monitor_placement == 'rx' and path.index(current_hop) == len(path) - 1: # last node:
504 insert_flow = True
stevenvanrossem4fac2af2016-12-22 01:26:02 +0100505 elif monitor_placement not in ['rx', 'tx']:
stevenvanrossembecc7c52016-11-07 05:52:01 +0100506 LOG.exception('invalid monitor command: {0}'.format(monitor_placement))
507
508
509 if self.controller == RemoteController and insert_flow:
510 ## set flow entry via ryu rest api
511 self._set_flow_entry_ryu_rest(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
512 break
513 elif insert_flow:
514 ## set flow entry via ovs-ofctl
515 self._set_flow_entry_dpctl(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
516 break
517
518 # take first link between switches by default
519 if isinstance( next_node, OVSSwitch ):
520 switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
521 current_hop = next_hop
522
523 return "path {2} between {0} and {1}".format(vnf_src_name, vnf_dst_name, cmd)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100524
525
stevenvanrossem461941c2016-05-10 11:41:29 +0200526 def setChain(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200527 """
528 Chain 2 vnf interfaces together by installing the flowrules in the switches along their path.
529 Currently the path is found using the default networkx shortest path function.
530 Each chain gets a unique vlan id , so different chains wil not interfere.
531
532 :param vnf_src_name: vnf name (string)
533 :param vnf_dst_name: vnf name (string)
534 :param vnf_src_interface: source interface name (string)
535 :param vnf_dst_interface: destination interface name (string)
536 :param cmd: 'add-flow' (default) to add a chain, 'del-flows' to remove a chain
537 :param cookie: cookie for the installed flowrules (can be used later as identifier for a set of installed chains)
538 :param match: custom match entry to be added to the flowrules (default: only in_port and vlan tag)
539 :param priority: custom flowrule priority
stevenvanrossembf1754e2016-11-17 10:20:52 +0100540 :param monitor: boolean to indicate whether this chain is a monitoring chain
541 :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 +0100542 :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 +0100543 :param path: custom path between the two VNFs (list of switches)
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200544 :return: output log string
545 """
stevenvanrossembecc7c52016-11-07 05:52:01 +0100546
547 # special procedure for monitoring flows
548 if kwargs.get('monitor'):
549
550 # check if chain already exists
551 found_chains = [chain_dict for chain_dict in self.installed_chains if
552 (chain_dict['vnf_src_name'] == vnf_src_name and chain_dict['vnf_src_interface'] == vnf_src_interface
553 and chain_dict['vnf_dst_name'] == vnf_dst_name and chain_dict['vnf_dst_interface'] == vnf_dst_interface)]
554
555 if len(found_chains) > 0:
556 # this chain exists, so need an extra monitoring flow
557 # assume only 1 chain per vnf/interface pair
558 LOG.debug('*** installing monitoring chain on top of pre-defined chain from {0}:{1} -> {2}:{3}'.
559 format(vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface))
560 tag = found_chains[0]['tag']
561 ret = self._addMonitorFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface,
562 tag=tag, table_id=0, **kwargs)
563 return ret
564 else:
565 # no chain existing (or E-LAN) -> install normal chain
stevenvanrossem4fac2af2016-12-22 01:26:02 +0100566 LOG.warning('*** installing monitoring chain without pre-defined NSD chain from {0}:{1} -> {2}:{3}'.
stevenvanrossembecc7c52016-11-07 05:52:01 +0100567 format(vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface))
568 pass
569
570
stevenvanrossem461941c2016-05-10 11:41:29 +0200571 cmd = kwargs.get('cmd')
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100572 if cmd == 'add-flow' or cmd == 'del-flows':
stevenvanrossem461941c2016-05-10 11:41:29 +0200573 ret = self._chainAddFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface, **kwargs)
574 if kwargs.get('bidirectional'):
jokac304ad32017-01-09 10:58:23 +0100575 if kwargs.get('path') is not None:
576 kwargs['path'] = list(reversed(kwargs.get('path')))
stevenvanrossem81955a52016-05-12 14:34:12 +0200577 ret = ret +'\n' + self._chainAddFlow(vnf_dst_name, vnf_src_name, vnf_dst_interface, vnf_src_interface, **kwargs)
stevenvanrossem9315da42016-04-11 12:10:06 +0200578
stevenvanrossem461941c2016-05-10 11:41:29 +0200579 else:
stevenvanrossem81955a52016-05-12 14:34:12 +0200580 ret = "Command unknown"
581
582 return ret
stevenvanrossem461941c2016-05-10 11:41:29 +0200583
584
585 def _chainAddFlow(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
586
peusterm53d3c142016-07-18 10:10:11 +0200587 src_sw = None
peusterm53d3c142016-07-18 10:10:11 +0200588 src_sw_inport_nr = 0
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100589 src_sw_inport_name = None
590 dst_sw = None
peusterm53d3c142016-07-18 10:10:11 +0200591 dst_sw_outport_nr = 0
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100592 dst_sw_outport_name = None
peusterm53d3c142016-07-18 10:10:11 +0200593
594 LOG.debug("call chainAddFlow vnf_src_name=%r, vnf_src_interface=%r, vnf_dst_name=%r, vnf_dst_interface=%r",
595 vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface)
596
stevenvanrossem9315da42016-04-11 12:10:06 +0200597 #check if port is specified (vnf:port)
stevenvanrossemed711fd2016-04-11 16:59:29 +0200598 if vnf_src_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200599 # take first interface by default
600 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
601 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200602 vnf_src_interface = link_dict[0]['src_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200603
stevenvanrossem9315da42016-04-11 12:10:06 +0200604 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
605 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
606 for link in link_dict:
peusterm53d3c142016-07-18 10:10:11 +0200607 if (link_dict[link]['src_port_id'] == vnf_src_interface or
608 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 +0200609 # found the right link and connected switch
stevenvanrossem9315da42016-04-11 12:10:06 +0200610 src_sw = connected_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200611 src_sw_inport_nr = link_dict[link]['dst_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100612 src_sw_inport_name = link_dict[link]['dst_port_name']
stevenvanrossem9315da42016-04-11 12:10:06 +0200613 break
614
stevenvanrossemed711fd2016-04-11 16:59:29 +0200615 if vnf_dst_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200616 # take first interface by default
617 connected_sw = self.DCNetwork_graph.neighbors(vnf_dst_name)[0]
618 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200619 vnf_dst_interface = link_dict[0]['dst_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200620
621 vnf_dst_name = vnf_dst_name.split(':')[0]
622 for connected_sw in self.DCNetwork_graph.neighbors(vnf_dst_name):
623 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
624 for link in link_dict:
peusterm53d3c142016-07-18 10:10:11 +0200625 if link_dict[link]['dst_port_id'] == vnf_dst_interface or \
626 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 +0200627 # found the right link and connected switch
628 dst_sw = connected_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200629 dst_sw_outport_nr = link_dict[link]['src_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100630 dst_sw_outport_name = link_dict[link]['src_port_name']
stevenvanrossem9315da42016-04-11 12:10:06 +0200631 break
632
jokac304ad32017-01-09 10:58:23 +0100633 path = kwargs.get('path')
634 if path is None:
635 # get shortest path
636 try:
637 # returns the first found shortest path
638 # if all shortest paths are wanted, use: all_shortest_paths
639 path = nx.shortest_path(self.DCNetwork_graph, src_sw, dst_sw, weight=kwargs.get('weight'))
640 except:
641 LOG.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
642 vnf_src_name, vnf_dst_name, src_sw, dst_sw))
643 LOG.debug("Graph nodes: %r" % self.DCNetwork_graph.nodes())
644 LOG.debug("Graph edges: %r" % self.DCNetwork_graph.edges())
645 for e, v in self.DCNetwork_graph.edges():
646 LOG.debug("%r" % self.DCNetwork_graph[e][v])
647 return "No path could be found between {0} and {1}".format(vnf_src_name, vnf_dst_name)
stevenvanrossem9315da42016-04-11 12:10:06 +0200648
peustermf9a817d2016-07-18 09:06:04 +0200649 LOG.info("Path between {0} and {1}: {2}".format(vnf_src_name, vnf_dst_name, path))
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100650
stevenvanrossem9315da42016-04-11 12:10:06 +0200651 current_hop = src_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200652 switch_inport_nr = src_sw_inport_nr
stevenvanrossem9315da42016-04-11 12:10:06 +0200653
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100654 # choose free vlan
stevenvanrossem27b6d952016-05-10 16:37:57 +0200655 cmd = kwargs.get('cmd')
656 vlan = None
stevenvanrossem263eee52017-02-08 01:04:36 +0100657 if cmd == 'add-flow':
stevenvanrossembecc7c52016-11-07 05:52:01 +0100658 if kwargs.get('tag'):
659 # use pre-defined tag
660 vlan = kwargs.get('tag')
661 else:
662 vlan = self.vlans.pop()
stevenvanrossem461941c2016-05-10 11:41:29 +0200663
stevenvanrossembecc7c52016-11-07 05:52:01 +0100664 # store the used vlan tag to identify this chain
665 if not kwargs.get('monitor'):
666 chain_dict = {}
667 chain_dict['vnf_src_name'] = vnf_src_name
668 chain_dict['vnf_dst_name'] = vnf_dst_name
669 chain_dict['vnf_src_interface'] = vnf_src_interface
670 chain_dict['vnf_dst_interface'] = vnf_dst_interface
671 chain_dict['tag'] = vlan
672 self.installed_chains.append(chain_dict)
673
674 #iterate through the path to install the flow-entries
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100675 for i in range(0,len(path)):
stevenvanrossem9315da42016-04-11 12:10:06 +0200676 current_node = self.getNodeByName(current_hop)
stevenvanrossem461941c2016-05-10 11:41:29 +0200677
jokac304ad32017-01-09 10:58:23 +0100678 if i < len(path) - 1:
679 next_hop = path[i + 1]
stevenvanrossem9315da42016-04-11 12:10:06 +0200680 else:
jokac304ad32017-01-09 10:58:23 +0100681 # last switch reached
stevenvanrossem9315da42016-04-11 12:10:06 +0200682 next_hop = vnf_dst_name
683
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100684 next_node = self.getNodeByName(next_hop)
685
686 if next_hop == vnf_dst_name:
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200687 switch_outport_nr = dst_sw_outport_nr
peustermf9a817d2016-07-18 09:06:04 +0200688 LOG.info("end node reached: {0}".format(vnf_dst_name))
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100689 elif not isinstance( next_node, OVSSwitch ):
peustermf9a817d2016-07-18 09:06:04 +0200690 LOG.info("Next node: {0} is not a switch".format(next_hop))
stevenvanrossemeefea6c2016-02-17 12:03:26 +0100691 return "Next node: {0} is not a switch".format(next_hop)
stevenvanrossem9315da42016-04-11 12:10:06 +0200692 else:
stevenvanrossemed711fd2016-04-11 16:59:29 +0200693 # take first link between switches by default
stevenvanrossem9315da42016-04-11 12:10:06 +0200694 index_edge_out = 0
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200695 switch_outport_nr = self.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
stevenvanrossem9315da42016-04-11 12:10:06 +0200696
stevenvanrossem9315da42016-04-11 12:10:06 +0200697
stevenvanrossem634c5ef2017-02-08 00:46:02 +0100698 # set OpenFlow entry
stevenvanrossem9315da42016-04-11 12:10:06 +0200699 if isinstance( current_node, OVSSwitch ):
stevenvanrossem461941c2016-05-10 11:41:29 +0200700 kwargs['vlan'] = vlan
701 kwargs['path'] = path
702 kwargs['current_hop'] = current_hop
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100703 kwargs['switch_inport_name'] = src_sw_inport_name
704 kwargs['switch_outport_name'] = dst_sw_outport_name
jokac304ad32017-01-09 10:58:23 +0100705 kwargs['pathindex'] = i
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200706
707 if self.controller == RemoteController:
708 ## set flow entry via ryu rest api
709 self._set_flow_entry_ryu_rest(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
710 else:
711 ## set flow entry via ovs-ofctl
712 self._set_flow_entry_dpctl(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
713
stevenvanrossemed711fd2016-04-11 16:59:29 +0200714 # take first link between switches by default
715 if isinstance( next_node, OVSSwitch ):
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200716 switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
stevenvanrossemed711fd2016-04-11 16:59:29 +0200717 current_hop = next_hop
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100718
stevenvanrossemae588012017-06-06 10:33:19 +0200719 flow_options = {
720 'priority':kwargs.get('priority', DEFAULT_PRIORITY),
721 'cookie':kwargs.get('cookie', DEFAULT_COOKIE),
722 'vlan':kwargs['vlan'],
723 'path':kwargs['path'],
724 'match_input':kwargs.get('match')
725 }
726 flow_options_str = json.dumps(flow_options, indent=1)
727 return "success: {2} between {0} and {1} with options: {3}".format(vnf_src_name, vnf_dst_name, cmd, flow_options_str)
stevenvanrossem27b6d952016-05-10 16:37:57 +0200728
729 def _set_flow_entry_ryu_rest(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
730 match = 'in_port=%s' % switch_inport_nr
731
732 cookie = kwargs.get('cookie')
733 match_input = kwargs.get('match')
734 cmd = kwargs.get('cmd')
735 path = kwargs.get('path')
jokac304ad32017-01-09 10:58:23 +0100736 index = kwargs.get('pathindex')
737
stevenvanrossem27b6d952016-05-10 16:37:57 +0200738 vlan = kwargs.get('vlan')
stevenvanrossemae588012017-06-06 10:33:19 +0200739 priority = kwargs.get('priority', DEFAULT_PRIORITY)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100740 # flag to not set the ovs port vlan tag
741 skip_vlan_tag = kwargs.get('skip_vlan_tag')
742 # table id to put this flowentry
743 table_id = kwargs.get('table_id')
744 if not table_id:
745 table_id = 0
stevenvanrossem27b6d952016-05-10 16:37:57 +0200746
747 s = ','
748 if match_input:
749 match = s.join([match, match_input])
750
751 flow = {}
752 flow['dpid'] = int(node.dpid, 16)
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200753
stevenvanrossem27b6d952016-05-10 16:37:57 +0200754 if cookie:
755 flow['cookie'] = int(cookie)
stevenvanrossem61699eb2016-08-05 15:57:59 +0200756 if priority:
757 flow['priority'] = int(priority)
stevenvanrossem27b6d952016-05-10 16:37:57 +0200758
stevenvanrossembecc7c52016-11-07 05:52:01 +0100759 flow['table_id'] = table_id
760
stevenvanrossem27b6d952016-05-10 16:37:57 +0200761 flow['actions'] = []
762
763 # possible Ryu actions, match fields:
764 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#add-a-flow-entry
765 if cmd == 'add-flow':
766 prefix = 'stats/flowentry/add'
stevenvanrossem27b6d952016-05-10 16:37:57 +0200767 if vlan != None:
jokac304ad32017-01-09 10:58:23 +0100768 if index == 0: # first node
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100769 # set vlan tag in ovs instance (to isolate E-LANs)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100770 if not skip_vlan_tag:
771 in_port_name = kwargs.get('switch_inport_name')
772 self._set_vlan_tag(node, in_port_name, vlan)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100773 # set vlan push action if more than 1 switch in the path
774 if len(path) > 1:
775 action = {}
776 action['type'] = 'PUSH_VLAN' # Push a new VLAN tag if a input frame is non-VLAN-tagged
777 action['ethertype'] = 33024 # Ethertype 0x8100(=33024): IEEE 802.1Q VLAN-tagged frame
778 flow['actions'].append(action)
779 action = {}
780 action['type'] = 'SET_FIELD'
781 action['field'] = 'vlan_vid'
stevenvanrossem9cc73602017-01-27 23:37:29 +0100782 # ryu expects the field to be masked
783 action['value'] = vlan | 0x1000
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100784 flow['actions'].append(action)
785
stevenvanrossem9cc73602017-01-27 23:37:29 +0100786 elif index == len(path) - 1: # last node
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100787 # set vlan tag in ovs instance (to isolate E-LANs)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100788 if not skip_vlan_tag:
789 out_port_name = kwargs.get('switch_outport_name')
790 self._set_vlan_tag(node, out_port_name, vlan)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100791 # set vlan pop action if more than 1 switch in the path
792 if len(path) > 1:
793 match += ',dl_vlan=%s' % vlan
794 action = {}
795 action['type'] = 'POP_VLAN'
796 flow['actions'].append(action)
797
stevenvanrossem27b6d952016-05-10 16:37:57 +0200798 else: # middle nodes
stevenvanrossem27b6d952016-05-10 16:37:57 +0200799 match += ',dl_vlan=%s' % vlan
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100800
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200801 # output action must come last
802 action = {}
803 action['type'] = 'OUTPUT'
804 action['port'] = switch_outport_nr
805 flow['actions'].append(action)
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200806
stevenvanrossem27b6d952016-05-10 16:37:57 +0200807 elif cmd == 'del-flows':
stevenvanrossem27b6d952016-05-10 16:37:57 +0200808 prefix = 'stats/flowentry/delete'
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200809
stevenvanrossem27b6d952016-05-10 16:37:57 +0200810 if cookie:
stevenvanrossem1ef77022016-05-12 16:36:10 +0200811 # TODO: add cookie_mask as argument
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200812 flow['cookie_mask'] = int('0xffffffffffffffff', 16) # need full mask to match complete cookie
stevenvanrossem27b6d952016-05-10 16:37:57 +0200813
814 action = {}
815 action['type'] = 'OUTPUT'
816 action['port'] = switch_outport_nr
817 flow['actions'].append(action)
818
819 flow['match'] = self._parse_match(match)
820 self.ryu_REST(prefix, data=flow)
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100821
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100822 def _set_vlan_tag(self, node, switch_port, tag):
823 node.vsctl('set', 'port {0} tag={1}'.format(switch_port,tag))
824 LOG.debug("set vlan in switch: {0} in_port: {1} vlan tag: {2}".format(node.name, switch_port, tag))
825
stevenvanrossem461941c2016-05-10 11:41:29 +0200826 def _set_flow_entry_dpctl(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100827
stevenvanrossem23c48092016-05-06 17:21:12 +0200828 match = 'in_port=%s' % switch_inport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200829
830 cookie = kwargs.get('cookie')
831 match_input = kwargs.get('match')
832 cmd = kwargs.get('cmd')
833 path = kwargs.get('path')
jokac304ad32017-01-09 10:58:23 +0100834 index = kwargs.get('pathindex')
stevenvanrossem461941c2016-05-10 11:41:29 +0200835 vlan = kwargs.get('vlan')
836
stevenvanrossem898a2af2016-05-06 18:28:57 +0200837 s = ','
838 if cookie:
839 cookie = 'cookie=%s' % cookie
840 match = s.join([cookie, match])
stevenvanrossem23c48092016-05-06 17:21:12 +0200841 if match_input:
stevenvanrossem23c48092016-05-06 17:21:12 +0200842 match = s.join([match, match_input])
stevenvanrossem23c48092016-05-06 17:21:12 +0200843 if cmd == 'add-flow':
844 action = 'action=%s' % switch_outport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200845 if vlan != None:
jokac304ad32017-01-09 10:58:23 +0100846 if index == 0: # first node
stevenvanrossem461941c2016-05-10 11:41:29 +0200847 action = ('action=mod_vlan_vid:%s' % vlan) + (',output=%s' % switch_outport_nr)
848 match = '-O OpenFlow13 ' + match
jokac304ad32017-01-09 10:58:23 +0100849 elif index == len(path) - 1: # last node
stevenvanrossem461941c2016-05-10 11:41:29 +0200850 match += ',dl_vlan=%s' % vlan
851 action = 'action=strip_vlan,output=%s' % switch_outport_nr
852 else: # middle nodes
853 match += ',dl_vlan=%s' % vlan
stevenvanrossem23c48092016-05-06 17:21:12 +0200854 ofcmd = s.join([match, action])
855 elif cmd == 'del-flows':
856 ofcmd = match
857 else:
858 ofcmd = ''
859
860 node.dpctl(cmd, ofcmd)
peustermf9a817d2016-07-18 09:06:04 +0200861 LOG.info("{3} in switch: {0} in_port: {1} out_port: {2}".format(node.name, switch_inport_nr,
stevenvanrossem461941c2016-05-10 11:41:29 +0200862 switch_outport_nr, cmd))
stevenvanrossem23c48092016-05-06 17:21:12 +0200863
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100864 # start Ryu Openflow controller as Remote Controller for the DCNetwork
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200865 def startRyu(self, learning_switch=True):
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100866 # start Ryu controller with rest-API
867 python_install_path = site.getsitepackages()[0]
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100868 # ryu default learning switch
869 #ryu_path = python_install_path + '/ryu/app/simple_switch_13.py'
870 #custom learning switch that installs a default NORMAL action in the ovs switches
871 dir_path = os.path.dirname(os.path.realpath(__file__))
872 ryu_path = dir_path + '/son_emu_simple_switch_13.py'
peustermde14f332016-03-15 16:14:21 +0100873 ryu_path2 = python_install_path + '/ryu/app/ofctl_rest.py'
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100874 # change the default Openflow controller port to 6653 (official IANA-assigned port number), as used by Mininet
875 # Ryu still uses 6633 as default
876 ryu_option = '--ofp-tcp-listen-port'
877 ryu_of_port = '6653'
peustermde14f332016-03-15 16:14:21 +0100878 ryu_cmd = 'ryu-manager'
peustermef6629e2016-03-14 17:21:56 +0100879 FNULL = open("/tmp/ryu.log", 'w')
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200880 if learning_switch:
881 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 +0100882 LOG.debug('starting ryu-controller with {0}'.format(ryu_path))
883 LOG.debug('starting ryu-controller with {0}'.format(ryu_path2))
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200884 else:
stevenvanrossem73efd192016-06-29 01:44:07 +0200885 # no learning switch, but with rest api
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200886 self.ryu_process = Popen([ryu_cmd, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100887 LOG.debug('starting ryu-controller with {0}'.format(ryu_path2))
peusterm391773a2016-03-14 17:40:43 +0100888 time.sleep(1)
889
peusterm8b04b532016-07-19 16:55:38 +0200890 def killRyu(self):
891 """
892 Stop the Ryu controller that might be started by son-emu.
893 :return:
894 """
895 # try it nicely
peustermde14f332016-03-15 16:14:21 +0100896 if self.ryu_process is not None:
peusterm391773a2016-03-14 17:40:43 +0100897 self.ryu_process.terminate()
898 self.ryu_process.kill()
peusterm8b04b532016-07-19 16:55:38 +0200899 # ensure its death ;-)
stevenvanrossem89706802016-07-19 02:54:45 +0200900 Popen(['pkill', '-f', 'ryu-manager'])
peusterm391773a2016-03-14 17:40:43 +0100901
stevenvanrossem27b6d952016-05-10 16:37:57 +0200902 def ryu_REST(self, prefix, dpid=None, data=None):
stevenvanrossem27b6d952016-05-10 16:37:57 +0200903
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200904 if dpid:
905 url = self.ryu_REST_api + '/' + str(prefix) + '/' + str(dpid)
906 else:
907 url = self.ryu_REST_api + '/' + str(prefix)
908 if data:
909 req = self.RyuSession.post(url, json=data)
910 else:
911 req = self.RyuSession.get(url)
912
913
914 # do extra logging if status code is not 200 (OK)
915 if req.status_code is not requests.codes.ok:
916 logging.info(
917 'type {0} encoding: {1} text: {2} headers: {3} history: {4}'.format(req.headers['content-type'],
918 req.encoding, req.text,
919 req.headers, req.history))
920 LOG.info('url: {0}'.format(str(url)))
921 if data: LOG.info('POST: {0}'.format(str(data)))
922 LOG.info('status: {0} reason: {1}'.format(req.status_code, req.reason))
923
924
925 if 'json' in req.headers['content-type']:
926 ret = req.json()
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200927 return ret
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200928
929 ret = req.text.rstrip()
930 return ret
931
stevenvanrossem27b6d952016-05-10 16:37:57 +0200932
933 # need to respect that some match fields must be integers
934 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#description-of-match-and-actions
935 def _parse_match(self, match):
936 matches = match.split(',')
937 dict = {}
938 for m in matches:
939 match = m.split('=')
940 if len(match) == 2:
941 try:
942 m2 = int(match[1], 0)
943 except:
944 m2 = match[1]
945
946 dict.update({match[0]:m2})
947 return dict
948
stevenvanrossem17b6e882017-05-04 16:51:34 +0200949 def find_connected_dc_interface(self, vnf_src_name, vnf_src_interface=None):
950
951 if vnf_src_interface is None:
952 # take first interface by default
953 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
954 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
955 vnf_src_interface = link_dict[0]['src_port_id']
956
stevenvanrossem566779d2016-11-07 06:33:44 +0100957 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
958 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
959 for link in link_dict:
960 if (link_dict[link]['src_port_id'] == vnf_src_interface or
961 link_dict[link]['src_port_name'] == vnf_src_interface): # Fix: we might also get interface names, e.g, from a son-emu-cli call
962 # found the right link and connected switch
963 src_sw = connected_sw
964 src_sw_inport_nr = link_dict[link]['dst_port_nr']
965 src_sw_inport_name = link_dict[link]['dst_port_name']
stevenvanrossem7062cee2016-12-22 10:31:38 +0100966 return src_sw_inport_name