blob: 3d9dcb078e947d8e358905b64c9bcc01faa0c421 [file] [log] [blame]
peustermcbcd4c22015-12-28 11:33:42 +01001"""
peusterm79ef6ae2016-07-08 13:53:57 +02002Copyright (c) 2015 SONATA-NFV and Paderborn University
3ALL RIGHTS RESERVED.
4
5Licensed under the Apache License, Version 2.0 (the "License");
6you may not use this file except in compliance with the License.
7You may obtain a copy of the License at
8
9 http://www.apache.org/licenses/LICENSE-2.0
10
11Unless required by applicable law or agreed to in writing, software
12distributed under the License is distributed on an "AS IS" BASIS,
13WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14See the License for the specific language governing permissions and
15limitations under the License.
16
17Neither the name of the SONATA-NFV [, ANY ADDITIONAL AFFILIATION]
18nor the names of its contributors may be used to endorse or promote
19products derived from this software without specific prior written
20permission.
21
22This work has been performed in the framework of the SONATA project,
23funded by the European Commission under Grant number 671517 through
24the Horizon 2020 and 5G-PPP programmes. The authors would like to
25acknowledge the contributions of their colleagues of the SONATA
26partner consortium (www.sonata-nfv.eu).
peustermcbcd4c22015-12-28 11:33:42 +010027"""
28import logging
29
stevenvanrossem9ebd0942016-02-22 10:13:05 +010030import site
peustermef6629e2016-03-14 17:21:56 +010031import time
stevenvanrossem9ebd0942016-02-22 10:13:05 +010032from subprocess import Popen
stevenvanrossem6b1d9b92016-05-02 13:10:40 +020033import re
stevenvanrossem3fc13932016-08-09 23:39:16 +020034import requests
stevenvanrossemc3a344f2016-11-04 19:34:47 +010035import os
stevenvanrossem9ebd0942016-02-22 10:13:05 +010036
peusterm5877ea22016-05-11 13:44:59 +020037from mininet.net import Containernet
peustermef6629e2016-03-14 17:21:56 +010038from mininet.node import Controller, DefaultController, OVSSwitch, OVSKernelSwitch, Docker, RemoteController
peustermcbcd4c22015-12-28 11:33:42 +010039from mininet.cli import CLI
peustermea8db832016-03-08 10:25:58 +010040from mininet.link import TCLink
peusterm8b04b532016-07-19 16:55:38 +020041from mininet.clean import cleanup
stevenvanrossemc5a536a2016-02-16 14:52:39 +010042import networkx as nx
cgeoffroy9524ad32016-03-03 18:24:15 +010043from emuvim.dcemulator.monitoring import DCNetworkMonitor
cgeoffroy9524ad32016-03-03 18:24:15 +010044from emuvim.dcemulator.node import Datacenter, EmulatorCompute
peusterm42f08be2016-03-10 21:56:34 +010045from emuvim.dcemulator.resourcemodel import ResourceModelRegistrar
peustermcbcd4c22015-12-28 11:33:42 +010046
peustermf9a817d2016-07-18 09:06:04 +020047LOG = logging.getLogger("dcemulator.net")
48LOG.setLevel(logging.DEBUG)
49
peusterm5877ea22016-05-11 13:44:59 +020050class DCNetwork(Containernet):
peusterme4e89d32016-01-07 09:14:54 +010051 """
peusterm5877ea22016-05-11 13:44:59 +020052 Wraps the original Mininet/Containernet class and provides
peusterme4e89d32016-01-07 09:14:54 +010053 methods to add data centers, switches, etc.
54
55 This class is used by topology definition scripts.
56 """
peustermcbcd4c22015-12-28 11:33:42 +010057
peusterm0ec25102016-04-16 02:16:20 +020058 def __init__(self, controller=RemoteController, monitor=False,
stevenvanrossemc3a344f2016-11-04 19:34:47 +010059 enable_learning=False, # learning switch behavior of the default ovs switches icw Ryu controller can be turned off/on, neede for E-LAN functionality
peusterma4d84792016-03-25 12:27:07 +010060 dc_emulation_max_cpu=1.0, # fraction of overall CPU time for emulation
61 dc_emulation_max_mem=512, # emulation max mem in MB
62 **kwargs):
peusterm42f08be2016-03-10 21:56:34 +010063 """
peusterm5877ea22016-05-11 13:44:59 +020064 Create an extended version of a Containernet network
peusterm42f08be2016-03-10 21:56:34 +010065 :param dc_emulation_max_cpu: max. CPU time used by containers in data centers
66 :param kwargs: path through for Mininet parameters
67 :return:
68 """
peusterm8b04b532016-07-19 16:55:38 +020069 # members
peustermcbcd4c22015-12-28 11:33:42 +010070 self.dcs = {}
peusterm8b04b532016-07-19 16:55:38 +020071 self.ryu_process = None
peusterm42f08be2016-03-10 21:56:34 +010072
peusterm8b04b532016-07-19 16:55:38 +020073 # always cleanup environment before we start the emulator
stevenvanrossem89706802016-07-19 02:54:45 +020074 self.killRyu()
peusterm8b04b532016-07-19 16:55:38 +020075 cleanup()
stevenvanrossem89706802016-07-19 02:54:45 +020076
peusterm293cbc32016-01-13 17:05:28 +010077 # call original Docker.__init__ and setup default controller
peusterm5877ea22016-05-11 13:44:59 +020078 Containernet.__init__(
stevenvanrossem7cd3c252016-05-11 22:55:15 +020079 self, switch=OVSKernelSwitch, controller=controller, **kwargs)
stevenvanrossemc5a536a2016-02-16 14:52:39 +010080
stevenvanrossemc3a344f2016-11-04 19:34:47 +010081 # default switch configuration
82 enable_ryu_learning = False
83 if enable_learning :
84 self.failMode = 'standalone'
85 enable_ryu_learning = True
86 else:
87 self.failMode = 'secure'
88
peustermde14f332016-03-15 16:14:21 +010089 # Ryu management
peustermde14f332016-03-15 16:14:21 +010090 if controller == RemoteController:
91 # start Ryu controller
stevenvanrossemc3a344f2016-11-04 19:34:47 +010092 self.startRyu(learning_switch=enable_ryu_learning)
stevenvanrossem9ebd0942016-02-22 10:13:05 +010093
peustermde14f332016-03-15 16:14:21 +010094 # add the specified controller
peustermef6629e2016-03-14 17:21:56 +010095 self.addController('c0', controller=controller)
96
97 # graph of the complete DC network
stevenvanrossemc1149022016-04-11 01:16:44 +020098 self.DCNetwork_graph = nx.MultiDiGraph()
peustermef6629e2016-03-14 17:21:56 +010099
stevenvanrossem461941c2016-05-10 11:41:29 +0200100 # initialize pool of vlan tags to setup the SDN paths
101 self.vlans = range(4096)[::-1]
102
stevenvanrossem27b6d952016-05-10 16:37:57 +0200103 # link to Ryu REST_API
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200104 ryu_ip = 'localhost'
stevenvanrossem27b6d952016-05-10 16:37:57 +0200105 ryu_port = '8080'
106 self.ryu_REST_api = 'http://{0}:{1}'.format(ryu_ip, ryu_port)
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200107 self.RyuSession = requests.Session()
stevenvanrossem27b6d952016-05-10 16:37:57 +0200108
peustermef6629e2016-03-14 17:21:56 +0100109 # monitoring agent
stevenvanrossem60670da2016-04-15 15:31:28 +0200110 if monitor:
111 self.monitor_agent = DCNetworkMonitor(self)
112 else:
113 self.monitor_agent = None
peustermef6629e2016-03-14 17:21:56 +0100114
peusterm42f08be2016-03-10 21:56:34 +0100115 # initialize resource model registrar
peusterma4d84792016-03-25 12:27:07 +0100116 self.rm_registrar = ResourceModelRegistrar(
117 dc_emulation_max_cpu, dc_emulation_max_mem)
peustermcbcd4c22015-12-28 11:33:42 +0100118
peusterm60bf8b82016-04-06 14:12:35 +0200119 def addDatacenter(self, label, metadata={}, resource_log_path=None):
peustermcbcd4c22015-12-28 11:33:42 +0100120 """
121 Create and add a logical cloud data center to the network.
122 """
peusterma47db032016-02-04 14:55:29 +0100123 if label in self.dcs:
124 raise Exception("Data center label already exists: %s" % label)
peusterm60bf8b82016-04-06 14:12:35 +0200125 dc = Datacenter(label, metadata=metadata, resource_log_path=resource_log_path)
peustermcbcd4c22015-12-28 11:33:42 +0100126 dc.net = self # set reference to network
peusterma47db032016-02-04 14:55:29 +0100127 self.dcs[label] = dc
peustermcbcd4c22015-12-28 11:33:42 +0100128 dc.create() # finally create the data center in our Mininet instance
peustermf9a817d2016-07-18 09:06:04 +0200129 LOG.info("added data center: %s" % label)
peustermcbcd4c22015-12-28 11:33:42 +0100130 return dc
131
peusterme6092692016-01-11 16:32:58 +0100132 def addLink(self, node1, node2, **params):
peusterm5b844a12016-01-11 15:58:15 +0100133 """
134 Able to handle Datacenter objects as link
135 end points.
136 """
peustermcbcd4c22015-12-28 11:33:42 +0100137 assert node1 is not None
138 assert node2 is not None
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100139
peustermcbcd4c22015-12-28 11:33:42 +0100140 # ensure type of node1
141 if isinstance( node1, basestring ):
142 if node1 in self.dcs:
143 node1 = self.dcs[node1].switch
peustermcbcd4c22015-12-28 11:33:42 +0100144 if isinstance( node1, Datacenter ):
145 node1 = node1.switch
146 # ensure type of node2
147 if isinstance( node2, basestring ):
148 if node2 in self.dcs:
149 node2 = self.dcs[node2].switch
peustermcbcd4c22015-12-28 11:33:42 +0100150 if isinstance( node2, Datacenter ):
151 node2 = node2.switch
peustermc3b977e2016-01-12 10:09:35 +0100152 # try to give containers a default IP
153 if isinstance( node1, Docker ):
peustermea8db832016-03-08 10:25:58 +0100154 if "params1" not in params:
peustermc3b977e2016-01-12 10:09:35 +0100155 params["params1"] = {}
peustermea8db832016-03-08 10:25:58 +0100156 if "ip" not in params["params1"]:
peustermc3b977e2016-01-12 10:09:35 +0100157 params["params1"]["ip"] = self.getNextIp()
158 if isinstance( node2, Docker ):
peustermea8db832016-03-08 10:25:58 +0100159 if "params2" not in params:
peustermc3b977e2016-01-12 10:09:35 +0100160 params["params2"] = {}
peustermea8db832016-03-08 10:25:58 +0100161 if "ip" not in params["params2"]:
peustermc3b977e2016-01-12 10:09:35 +0100162 params["params2"]["ip"] = self.getNextIp()
peustermea8db832016-03-08 10:25:58 +0100163 # ensure that we allow TCLinks between data centers
164 # TODO this is not optimal, we use cls=Link for containers and TCLink for data centers
peusterm5877ea22016-05-11 13:44:59 +0200165 # see Containernet issue: https://github.com/mpeuster/containernet/issues/3
peustermea8db832016-03-08 10:25:58 +0100166 if "cls" not in params:
167 params["cls"] = TCLink
peustermc3b977e2016-01-12 10:09:35 +0100168
peusterm5877ea22016-05-11 13:44:59 +0200169 link = Containernet.addLink(self, node1, node2, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100170
stevenvanrossemc1149022016-04-11 01:16:44 +0200171 # try to give container interfaces a default id
172 node1_port_id = node1.ports[link.intf1]
173 if isinstance(node1, Docker):
174 if "id" in params["params1"]:
175 node1_port_id = params["params1"]["id"]
stevenvanrossem5b376412016-05-04 15:34:49 +0200176 node1_port_name = link.intf1.name
stevenvanrossemc1149022016-04-11 01:16:44 +0200177
178 node2_port_id = node2.ports[link.intf2]
179 if isinstance(node2, Docker):
180 if "id" in params["params2"]:
181 node2_port_id = params["params2"]["id"]
stevenvanrossem5b376412016-05-04 15:34:49 +0200182 node2_port_name = link.intf2.name
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200183
184
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100185 # add edge and assigned port number to graph in both directions between node1 and node2
stevenvanrossemc1149022016-04-11 01:16:44 +0200186 # port_id: id given in descriptor (if available, otherwise same as port)
peusterm5877ea22016-05-11 13:44:59 +0200187 # port: portnumber assigned by Containernet
stevenvanrossemc1149022016-04-11 01:16:44 +0200188
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200189 attr_dict = {}
190 # possible weight metrics allowed by TClink class:
191 weight_metrics = ['bw', 'delay', 'jitter', 'loss']
192 edge_attributes = [p for p in params if p in weight_metrics]
193 for attr in edge_attributes:
194 # if delay: strip ms (need number as weight in graph)
195 match = re.search('([0-9]*\.?[0-9]+)', params[attr])
196 if match:
197 attr_number = match.group(1)
198 else:
199 attr_number = None
200 attr_dict[attr] = attr_number
201
202
stevenvanrossem5b376412016-05-04 15:34:49 +0200203 attr_dict2 = {'src_port_id': node1_port_id, 'src_port_nr': node1.ports[link.intf1],
204 'src_port_name': node1_port_name,
205 'dst_port_id': node2_port_id, 'dst_port_nr': node2.ports[link.intf2],
206 'dst_port_name': node2_port_name}
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200207 attr_dict2.update(attr_dict)
208 self.DCNetwork_graph.add_edge(node1.name, node2.name, attr_dict=attr_dict2)
209
stevenvanrossem5b376412016-05-04 15:34:49 +0200210 attr_dict2 = {'src_port_id': node2_port_id, 'src_port_nr': node2.ports[link.intf2],
211 'src_port_name': node2_port_name,
212 'dst_port_id': node1_port_id, 'dst_port_nr': node1.ports[link.intf1],
213 'dst_port_name': node1_port_name}
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200214 attr_dict2.update(attr_dict)
215 self.DCNetwork_graph.add_edge(node2.name, node1.name, attr_dict=attr_dict2)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100216
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100217 LOG.debug("addLink: n1={0} intf1={1} -- n2={2} intf2={3}".format(
218 str(node1),node1_port_name, str(node2), node2_port_name))
219
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100220 return link
peustermcbcd4c22015-12-28 11:33:42 +0100221
peusterma47db032016-02-04 14:55:29 +0100222 def addDocker( self, label, **params ):
peusterm5b844a12016-01-11 15:58:15 +0100223 """
peusterm293cbc32016-01-13 17:05:28 +0100224 Wrapper for addDocker method to use custom container class.
peusterm5b844a12016-01-11 15:58:15 +0100225 """
stevenvanrossem8fbf9782016-02-17 11:40:23 +0100226 self.DCNetwork_graph.add_node(label)
peusterm5877ea22016-05-11 13:44:59 +0200227 return Containernet.addDocker(self, label, cls=EmulatorCompute, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100228
stevenvanrossem8fbf9782016-02-17 11:40:23 +0100229 def removeDocker( self, label, **params ):
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100230 """
231 Wrapper for removeDocker method to update graph.
232 """
stevenvanrossem8fbf9782016-02-17 11:40:23 +0100233 self.DCNetwork_graph.remove_node(label)
peusterm5877ea22016-05-11 13:44:59 +0200234 return Containernet.removeDocker(self, label, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100235
236 def addSwitch( self, name, add_to_graph=True, **params ):
237 """
238 Wrapper for addSwitch method to store switch also in graph.
239 """
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100240
241 # add this switch to the global topology overview
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100242 if add_to_graph:
243 self.DCNetwork_graph.add_node(name)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100244
245 # set the learning switch behavior
246 if 'failMode' in params :
247 failMode = params['failMode']
248 else :
249 failMode = self.failMode
250
251 s = Containernet.addSwitch(self, name, protocols='OpenFlow10,OpenFlow12,OpenFlow13', failMode=failMode, **params)
252
253 # set flow entry that enables learning switch behavior (needed to enable E-LAN functionality)
254 #LOG.info('failmode {0}'.format(failMode))
255 #if failMode == 'standalone' :
256 # LOG.info('add NORMAL')
257 # s.dpctl('add-flow', 'actions=NORMAL')
258
259 return s
peustermc3b977e2016-01-12 10:09:35 +0100260
peustermbd44f4a2016-01-13 14:53:30 +0100261 def getAllContainers(self):
262 """
263 Returns a list with all containers within all data centers.
264 """
265 all_containers = []
266 for dc in self.dcs.itervalues():
267 all_containers += dc.listCompute()
268 return all_containers
269
peustermcbcd4c22015-12-28 11:33:42 +0100270 def start(self):
271 # start
272 for dc in self.dcs.itervalues():
273 dc.start()
peusterm5877ea22016-05-11 13:44:59 +0200274 Containernet.start(self)
peustermcbcd4c22015-12-28 11:33:42 +0100275
276 def stop(self):
stevenvanrossem60670da2016-04-15 15:31:28 +0200277
stevenvanrossemc6abf132016-04-14 11:15:58 +0200278 # stop the monitor agent
stevenvanrossem60670da2016-04-15 15:31:28 +0200279 if self.monitor_agent is not None:
280 self.monitor_agent.stop()
peustermcbcd4c22015-12-28 11:33:42 +0100281
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200282 # stop emulator net
peusterm5877ea22016-05-11 13:44:59 +0200283 Containernet.stop(self)
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200284
285 # stop Ryu controller
peusterm8b04b532016-07-19 16:55:38 +0200286 self.killRyu()
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200287
288
peustermcbcd4c22015-12-28 11:33:42 +0100289 def CLI(self):
peusterm293cbc32016-01-13 17:05:28 +0100290 CLI(self)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100291
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100292 def setLAN(self, vnf_list):
293 """
294 setup an E-LAN network by assigning the same VLAN tag to each DC interface of the VNFs in the E-LAN
295
296 :param vnf_list: names of the VNFs in this E-LAN [{name:,interface:},...]
297 :return:
298 """
299 src_sw = None
300 src_sw_inport_nr = 0
301 src_sw_inport_name = None
302
303 # get a vlan tag for this E-LAN
304 vlan = self.vlans.pop()
305
306 for vnf in vnf_list:
307 vnf_src_name = vnf['name']
308 vnf_src_interface = vnf['interface']
309
310 # check if port is specified (vnf:port)
311 if vnf_src_interface is None:
312 # take first interface by default
313 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
314 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
315 vnf_src_interface = link_dict[0]['src_port_id']
316
317 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
318 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
319 for link in link_dict:
320 if (link_dict[link]['src_port_id'] == vnf_src_interface or
321 link_dict[link]['src_port_name'] == vnf_src_interface): # Fix: we might also get interface names, e.g, from a son-emu-cli call
322 # found the right link and connected switch
323 src_sw = connected_sw
324 src_sw_inport_nr = link_dict[link]['dst_port_nr']
325 src_sw_inport_name = link_dict[link]['dst_port_name']
326 break
327
328 # set the tag on the dc switch interface
329 LOG.debug('set E-LAN: vnf name: {0} interface: {1} tag: {2}'.format(vnf_src_name, vnf_src_interface,vlan))
330 switch_node = self.getNodeByName(src_sw)
331 self._set_vlan_tag(switch_node, src_sw_inport_name, vlan)
332
333
334
335
336
stevenvanrossem461941c2016-05-10 11:41:29 +0200337 def setChain(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200338 """
339 Chain 2 vnf interfaces together by installing the flowrules in the switches along their path.
340 Currently the path is found using the default networkx shortest path function.
341 Each chain gets a unique vlan id , so different chains wil not interfere.
342
343 :param vnf_src_name: vnf name (string)
344 :param vnf_dst_name: vnf name (string)
345 :param vnf_src_interface: source interface name (string)
346 :param vnf_dst_interface: destination interface name (string)
347 :param cmd: 'add-flow' (default) to add a chain, 'del-flows' to remove a chain
348 :param cookie: cookie for the installed flowrules (can be used later as identifier for a set of installed chains)
349 :param match: custom match entry to be added to the flowrules (default: only in_port and vlan tag)
350 :param priority: custom flowrule priority
351 :return: output log string
352 """
stevenvanrossem461941c2016-05-10 11:41:29 +0200353 cmd = kwargs.get('cmd')
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100354 if cmd == 'add-flow' or cmd == 'del-flows':
stevenvanrossem461941c2016-05-10 11:41:29 +0200355 ret = self._chainAddFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface, **kwargs)
356 if kwargs.get('bidirectional'):
stevenvanrossem81955a52016-05-12 14:34:12 +0200357 ret = ret +'\n' + self._chainAddFlow(vnf_dst_name, vnf_src_name, vnf_dst_interface, vnf_src_interface, **kwargs)
stevenvanrossem9315da42016-04-11 12:10:06 +0200358
stevenvanrossem461941c2016-05-10 11:41:29 +0200359 else:
stevenvanrossem81955a52016-05-12 14:34:12 +0200360 ret = "Command unknown"
361
362 return ret
stevenvanrossem461941c2016-05-10 11:41:29 +0200363
364
365 def _chainAddFlow(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
366
peusterm53d3c142016-07-18 10:10:11 +0200367 src_sw = None
peusterm53d3c142016-07-18 10:10:11 +0200368 src_sw_inport_nr = 0
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100369 src_sw_inport_name = None
370 dst_sw = None
peusterm53d3c142016-07-18 10:10:11 +0200371 dst_sw_outport_nr = 0
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100372 dst_sw_outport_name = None
peusterm53d3c142016-07-18 10:10:11 +0200373
374 LOG.debug("call chainAddFlow vnf_src_name=%r, vnf_src_interface=%r, vnf_dst_name=%r, vnf_dst_interface=%r",
375 vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface)
376
stevenvanrossem9315da42016-04-11 12:10:06 +0200377 #check if port is specified (vnf:port)
stevenvanrossemed711fd2016-04-11 16:59:29 +0200378 if vnf_src_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200379 # take first interface by default
380 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
381 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200382 vnf_src_interface = link_dict[0]['src_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200383
stevenvanrossem9315da42016-04-11 12:10:06 +0200384 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
385 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
386 for link in link_dict:
peusterm53d3c142016-07-18 10:10:11 +0200387 if (link_dict[link]['src_port_id'] == vnf_src_interface or
388 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 +0200389 # found the right link and connected switch
stevenvanrossem9315da42016-04-11 12:10:06 +0200390 src_sw = connected_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200391 src_sw_inport_nr = link_dict[link]['dst_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100392 src_sw_inport_name = link_dict[link]['dst_port_name']
stevenvanrossem9315da42016-04-11 12:10:06 +0200393 break
394
stevenvanrossemed711fd2016-04-11 16:59:29 +0200395 if vnf_dst_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200396 # take first interface by default
397 connected_sw = self.DCNetwork_graph.neighbors(vnf_dst_name)[0]
398 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200399 vnf_dst_interface = link_dict[0]['dst_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200400
401 vnf_dst_name = vnf_dst_name.split(':')[0]
402 for connected_sw in self.DCNetwork_graph.neighbors(vnf_dst_name):
403 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
404 for link in link_dict:
peusterm53d3c142016-07-18 10:10:11 +0200405 if link_dict[link]['dst_port_id'] == vnf_dst_interface or \
406 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 +0200407 # found the right link and connected switch
408 dst_sw = connected_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200409 dst_sw_outport_nr = link_dict[link]['src_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100410 dst_sw_outport_name = link_dict[link]['src_port_name']
stevenvanrossem9315da42016-04-11 12:10:06 +0200411 break
412
413
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100414 # get shortest path
stevenvanrossem9315da42016-04-11 12:10:06 +0200415 try:
stevenvanrossem1f68afb2016-05-02 15:36:04 +0200416 # returns the first found shortest path
417 # if all shortest paths are wanted, use: all_shortest_paths
stevenvanrossem461941c2016-05-10 11:41:29 +0200418 path = nx.shortest_path(self.DCNetwork_graph, src_sw, dst_sw, weight=kwargs.get('weight'))
stevenvanrossem9315da42016-04-11 12:10:06 +0200419 except:
peusterm53d3c142016-07-18 10:10:11 +0200420 LOG.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
421 vnf_src_name, vnf_dst_name, src_sw, dst_sw))
peustermf9a817d2016-07-18 09:06:04 +0200422 LOG.debug("Graph nodes: %r" % self.DCNetwork_graph.nodes())
423 LOG.debug("Graph edges: %r" % self.DCNetwork_graph.edges())
peusterm53d3c142016-07-18 10:10:11 +0200424 for e, v in self.DCNetwork_graph.edges():
425 LOG.debug("%r" % self.DCNetwork_graph[e][v])
stevenvanrossem9315da42016-04-11 12:10:06 +0200426 return "No path could be found between {0} and {1}".format(vnf_src_name, vnf_dst_name)
427
peustermf9a817d2016-07-18 09:06:04 +0200428 LOG.info("Path between {0} and {1}: {2}".format(vnf_src_name, vnf_dst_name, path))
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100429
stevenvanrossem9315da42016-04-11 12:10:06 +0200430 current_hop = src_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200431 switch_inport_nr = src_sw_inport_nr
stevenvanrossem9315da42016-04-11 12:10:06 +0200432
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100433 # choose free vlan
434 ## if path contains more than 1 switch
stevenvanrossem27b6d952016-05-10 16:37:57 +0200435 cmd = kwargs.get('cmd')
436 vlan = None
437 if cmd == 'add-flow':
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100438 vlan = self.vlans.pop()
439 ##if len(path) > 1:
440 ## vlan = self.vlans.pop()
stevenvanrossem461941c2016-05-10 11:41:29 +0200441
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100442 for i in range(0,len(path)):
stevenvanrossem9315da42016-04-11 12:10:06 +0200443 current_node = self.getNodeByName(current_hop)
stevenvanrossem461941c2016-05-10 11:41:29 +0200444
stevenvanrossem9315da42016-04-11 12:10:06 +0200445 if path.index(current_hop) < len(path)-1:
446 next_hop = path[path.index(current_hop)+1]
447 else:
448 #last switch reached
449 next_hop = vnf_dst_name
450
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100451 next_node = self.getNodeByName(next_hop)
452
453 if next_hop == vnf_dst_name:
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200454 switch_outport_nr = dst_sw_outport_nr
peustermf9a817d2016-07-18 09:06:04 +0200455 LOG.info("end node reached: {0}".format(vnf_dst_name))
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100456 elif not isinstance( next_node, OVSSwitch ):
peustermf9a817d2016-07-18 09:06:04 +0200457 LOG.info("Next node: {0} is not a switch".format(next_hop))
stevenvanrossemeefea6c2016-02-17 12:03:26 +0100458 return "Next node: {0} is not a switch".format(next_hop)
stevenvanrossem9315da42016-04-11 12:10:06 +0200459 else:
stevenvanrossemed711fd2016-04-11 16:59:29 +0200460 # take first link between switches by default
stevenvanrossem9315da42016-04-11 12:10:06 +0200461 index_edge_out = 0
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200462 switch_outport_nr = self.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
stevenvanrossem9315da42016-04-11 12:10:06 +0200463
stevenvanrossem9315da42016-04-11 12:10:06 +0200464
stevenvanrossem461941c2016-05-10 11:41:29 +0200465 # set of entry via ovs-ofctl
stevenvanrossem9315da42016-04-11 12:10:06 +0200466 if isinstance( current_node, OVSSwitch ):
stevenvanrossem461941c2016-05-10 11:41:29 +0200467 kwargs['vlan'] = vlan
468 kwargs['path'] = path
469 kwargs['current_hop'] = current_hop
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100470 kwargs['switch_inport_name'] = src_sw_inport_name
471 kwargs['switch_outport_name'] = dst_sw_outport_name
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200472
473 if self.controller == RemoteController:
474 ## set flow entry via ryu rest api
475 self._set_flow_entry_ryu_rest(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
476 else:
477 ## set flow entry via ovs-ofctl
478 self._set_flow_entry_dpctl(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
479
480
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100481
stevenvanrossemed711fd2016-04-11 16:59:29 +0200482 # take first link between switches by default
483 if isinstance( next_node, OVSSwitch ):
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200484 switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
stevenvanrossemed711fd2016-04-11 16:59:29 +0200485 current_hop = next_hop
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100486
stevenvanrossem27b6d952016-05-10 16:37:57 +0200487 return "path {2} between {0} and {1}".format(vnf_src_name, vnf_dst_name, cmd)
488
489 def _set_flow_entry_ryu_rest(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
490 match = 'in_port=%s' % switch_inport_nr
491
492 cookie = kwargs.get('cookie')
493 match_input = kwargs.get('match')
494 cmd = kwargs.get('cmd')
495 path = kwargs.get('path')
496 current_hop = kwargs.get('current_hop')
497 vlan = kwargs.get('vlan')
stevenvanrossem61699eb2016-08-05 15:57:59 +0200498 priority = kwargs.get('priority')
stevenvanrossem27b6d952016-05-10 16:37:57 +0200499
500 s = ','
501 if match_input:
502 match = s.join([match, match_input])
503
504 flow = {}
505 flow['dpid'] = int(node.dpid, 16)
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200506
stevenvanrossem27b6d952016-05-10 16:37:57 +0200507 if cookie:
508 flow['cookie'] = int(cookie)
stevenvanrossem61699eb2016-08-05 15:57:59 +0200509 if priority:
510 flow['priority'] = int(priority)
stevenvanrossem27b6d952016-05-10 16:37:57 +0200511
512 flow['actions'] = []
513
514 # possible Ryu actions, match fields:
515 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#add-a-flow-entry
516 if cmd == 'add-flow':
517 prefix = 'stats/flowentry/add'
stevenvanrossem27b6d952016-05-10 16:37:57 +0200518 if vlan != None:
519 if path.index(current_hop) == 0: # first node
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100520 # set vlan tag in ovs instance (to isolate E-LANs)
521 in_port_name = kwargs.get('switch_inport_name')
522 self._set_vlan_tag(node, in_port_name, vlan)
523 # set vlan push action if more than 1 switch in the path
524 if len(path) > 1:
525 action = {}
526 action['type'] = 'PUSH_VLAN' # Push a new VLAN tag if a input frame is non-VLAN-tagged
527 action['ethertype'] = 33024 # Ethertype 0x8100(=33024): IEEE 802.1Q VLAN-tagged frame
528 flow['actions'].append(action)
529 action = {}
530 action['type'] = 'SET_FIELD'
531 action['field'] = 'vlan_vid'
532 action['value'] = vlan
533 flow['actions'].append(action)
534
535 if path.index(current_hop) == len(path) - 1: # last node
536 # set vlan tag in ovs instance (to isolate E-LANs)
537 out_port_name = kwargs.get('switch_outport_name')
538 self._set_vlan_tag(node, out_port_name, vlan)
539 # set vlan pop action if more than 1 switch in the path
540 if len(path) > 1:
541 match += ',dl_vlan=%s' % vlan
542 action = {}
543 action['type'] = 'POP_VLAN'
544 flow['actions'].append(action)
545
546 if 0 < path.index(current_hop) < (len(path) - 1): # middle nodes
stevenvanrossem27b6d952016-05-10 16:37:57 +0200547 match += ',dl_vlan=%s' % vlan
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100548
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200549 # output action must come last
550 action = {}
551 action['type'] = 'OUTPUT'
552 action['port'] = switch_outport_nr
553 flow['actions'].append(action)
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200554
stevenvanrossem27b6d952016-05-10 16:37:57 +0200555 elif cmd == 'del-flows':
stevenvanrossem27b6d952016-05-10 16:37:57 +0200556 prefix = 'stats/flowentry/delete'
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200557
stevenvanrossem27b6d952016-05-10 16:37:57 +0200558 if cookie:
stevenvanrossem1ef77022016-05-12 16:36:10 +0200559 # TODO: add cookie_mask as argument
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200560 flow['cookie_mask'] = int('0xffffffffffffffff', 16) # need full mask to match complete cookie
stevenvanrossem27b6d952016-05-10 16:37:57 +0200561
562 action = {}
563 action['type'] = 'OUTPUT'
564 action['port'] = switch_outport_nr
565 flow['actions'].append(action)
566
567 flow['match'] = self._parse_match(match)
568 self.ryu_REST(prefix, data=flow)
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100569
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100570 def _set_vlan_tag(self, node, switch_port, tag):
571 node.vsctl('set', 'port {0} tag={1}'.format(switch_port,tag))
572 LOG.debug("set vlan in switch: {0} in_port: {1} vlan tag: {2}".format(node.name, switch_port, tag))
573
stevenvanrossem461941c2016-05-10 11:41:29 +0200574 def _set_flow_entry_dpctl(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100575
stevenvanrossem23c48092016-05-06 17:21:12 +0200576 match = 'in_port=%s' % switch_inport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200577
578 cookie = kwargs.get('cookie')
579 match_input = kwargs.get('match')
580 cmd = kwargs.get('cmd')
581 path = kwargs.get('path')
582 current_hop = kwargs.get('current_hop')
583 vlan = kwargs.get('vlan')
584
stevenvanrossem898a2af2016-05-06 18:28:57 +0200585 s = ','
586 if cookie:
587 cookie = 'cookie=%s' % cookie
588 match = s.join([cookie, match])
stevenvanrossem23c48092016-05-06 17:21:12 +0200589 if match_input:
stevenvanrossem23c48092016-05-06 17:21:12 +0200590 match = s.join([match, match_input])
stevenvanrossem23c48092016-05-06 17:21:12 +0200591 if cmd == 'add-flow':
592 action = 'action=%s' % switch_outport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200593 if vlan != None:
594 if path.index(current_hop) == 0: # first node
595 action = ('action=mod_vlan_vid:%s' % vlan) + (',output=%s' % switch_outport_nr)
596 match = '-O OpenFlow13 ' + match
597 elif path.index(current_hop) == len(path) - 1: # last node
598 match += ',dl_vlan=%s' % vlan
599 action = 'action=strip_vlan,output=%s' % switch_outport_nr
600 else: # middle nodes
601 match += ',dl_vlan=%s' % vlan
stevenvanrossem23c48092016-05-06 17:21:12 +0200602 ofcmd = s.join([match, action])
603 elif cmd == 'del-flows':
604 ofcmd = match
605 else:
606 ofcmd = ''
607
608 node.dpctl(cmd, ofcmd)
peustermf9a817d2016-07-18 09:06:04 +0200609 LOG.info("{3} in switch: {0} in_port: {1} out_port: {2}".format(node.name, switch_inport_nr,
stevenvanrossem461941c2016-05-10 11:41:29 +0200610 switch_outport_nr, cmd))
stevenvanrossem23c48092016-05-06 17:21:12 +0200611
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100612 # start Ryu Openflow controller as Remote Controller for the DCNetwork
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200613 def startRyu(self, learning_switch=True):
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100614 # start Ryu controller with rest-API
615 python_install_path = site.getsitepackages()[0]
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100616 # ryu default learning switch
617 #ryu_path = python_install_path + '/ryu/app/simple_switch_13.py'
618 #custom learning switch that installs a default NORMAL action in the ovs switches
619 dir_path = os.path.dirname(os.path.realpath(__file__))
620 ryu_path = dir_path + '/son_emu_simple_switch_13.py'
peustermde14f332016-03-15 16:14:21 +0100621 ryu_path2 = python_install_path + '/ryu/app/ofctl_rest.py'
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100622 # change the default Openflow controller port to 6653 (official IANA-assigned port number), as used by Mininet
623 # Ryu still uses 6633 as default
624 ryu_option = '--ofp-tcp-listen-port'
625 ryu_of_port = '6653'
peustermde14f332016-03-15 16:14:21 +0100626 ryu_cmd = 'ryu-manager'
peustermef6629e2016-03-14 17:21:56 +0100627 FNULL = open("/tmp/ryu.log", 'w')
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200628 if learning_switch:
629 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 +0100630 LOG.debug('starting ryu-controller with {0}'.format(ryu_path))
631 LOG.debug('starting ryu-controller with {0}'.format(ryu_path2))
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200632 else:
stevenvanrossem73efd192016-06-29 01:44:07 +0200633 # no learning switch, but with rest api
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200634 self.ryu_process = Popen([ryu_cmd, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100635 LOG.debug('starting ryu-controller with {0}'.format(ryu_path2))
peusterm391773a2016-03-14 17:40:43 +0100636 time.sleep(1)
637
peusterm8b04b532016-07-19 16:55:38 +0200638 def killRyu(self):
639 """
640 Stop the Ryu controller that might be started by son-emu.
641 :return:
642 """
643 # try it nicely
peustermde14f332016-03-15 16:14:21 +0100644 if self.ryu_process is not None:
peusterm391773a2016-03-14 17:40:43 +0100645 self.ryu_process.terminate()
646 self.ryu_process.kill()
peusterm8b04b532016-07-19 16:55:38 +0200647 # ensure its death ;-)
stevenvanrossem89706802016-07-19 02:54:45 +0200648 Popen(['pkill', '-f', 'ryu-manager'])
peusterm391773a2016-03-14 17:40:43 +0100649
stevenvanrossem27b6d952016-05-10 16:37:57 +0200650 def ryu_REST(self, prefix, dpid=None, data=None):
stevenvanrossem27b6d952016-05-10 16:37:57 +0200651
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200652 if dpid:
653 url = self.ryu_REST_api + '/' + str(prefix) + '/' + str(dpid)
654 else:
655 url = self.ryu_REST_api + '/' + str(prefix)
656 if data:
657 req = self.RyuSession.post(url, json=data)
658 else:
659 req = self.RyuSession.get(url)
660
661
662 # do extra logging if status code is not 200 (OK)
663 if req.status_code is not requests.codes.ok:
664 logging.info(
665 'type {0} encoding: {1} text: {2} headers: {3} history: {4}'.format(req.headers['content-type'],
666 req.encoding, req.text,
667 req.headers, req.history))
668 LOG.info('url: {0}'.format(str(url)))
669 if data: LOG.info('POST: {0}'.format(str(data)))
670 LOG.info('status: {0} reason: {1}'.format(req.status_code, req.reason))
671
672
673 if 'json' in req.headers['content-type']:
674 ret = req.json()
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200675 return ret
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200676
677 ret = req.text.rstrip()
678 return ret
679
stevenvanrossem27b6d952016-05-10 16:37:57 +0200680
681 # need to respect that some match fields must be integers
682 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#description-of-match-and-actions
683 def _parse_match(self, match):
684 matches = match.split(',')
685 dict = {}
686 for m in matches:
687 match = m.split('=')
688 if len(match) == 2:
689 try:
690 m2 = int(match[1], 0)
691 except:
692 m2 = match[1]
693
694 dict.update({match[0]:m2})
695 return dict
696