blob: 2c5ce14227133d9b5f89ca9d03f8b37273d8f3d7 [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
33import os
stevenvanrossem6b1d9b92016-05-02 13:10:40 +020034import re
stevenvanrossem27b6d952016-05-10 16:37:57 +020035import urllib2
36from functools import partial
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
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
peusterm5877ea22016-05-11 13:44:59 +020047class DCNetwork(Containernet):
peusterme4e89d32016-01-07 09:14:54 +010048 """
peusterm5877ea22016-05-11 13:44:59 +020049 Wraps the original Mininet/Containernet class and provides
peusterme4e89d32016-01-07 09:14:54 +010050 methods to add data centers, switches, etc.
51
52 This class is used by topology definition scripts.
53 """
peustermcbcd4c22015-12-28 11:33:42 +010054
peusterm0ec25102016-04-16 02:16:20 +020055 def __init__(self, controller=RemoteController, monitor=False,
stevenvanrossem7cd3c252016-05-11 22:55:15 +020056 enable_learning = True, # in case of RemoteController (Ryu), learning switch behavior can be turned off/on
peusterma4d84792016-03-25 12:27:07 +010057 dc_emulation_max_cpu=1.0, # fraction of overall CPU time for emulation
58 dc_emulation_max_mem=512, # emulation max mem in MB
59 **kwargs):
peusterm42f08be2016-03-10 21:56:34 +010060 """
peusterm5877ea22016-05-11 13:44:59 +020061 Create an extended version of a Containernet network
peusterm42f08be2016-03-10 21:56:34 +010062 :param dc_emulation_max_cpu: max. CPU time used by containers in data centers
63 :param kwargs: path through for Mininet parameters
64 :return:
65 """
peustermcbcd4c22015-12-28 11:33:42 +010066 self.dcs = {}
peusterm42f08be2016-03-10 21:56:34 +010067
stevenvanrossem89706802016-07-19 02:54:45 +020068 # make sure any remaining Ryu processes are killed
69 self.killRyu()
70 # make sure no containers are left over from a previous emulator run.
71 self.removeLeftoverContainers()
72
peusterm293cbc32016-01-13 17:05:28 +010073 # call original Docker.__init__ and setup default controller
peusterm5877ea22016-05-11 13:44:59 +020074 Containernet.__init__(
stevenvanrossem7cd3c252016-05-11 22:55:15 +020075 self, switch=OVSKernelSwitch, controller=controller, **kwargs)
stevenvanrossemc5a536a2016-02-16 14:52:39 +010076
peusterm293cbc32016-01-13 17:05:28 +010077
peustermde14f332016-03-15 16:14:21 +010078 # Ryu management
79 self.ryu_process = None
80 if controller == RemoteController:
81 # start Ryu controller
stevenvanrossem7cd3c252016-05-11 22:55:15 +020082 self.startRyu(learning_switch=enable_learning)
stevenvanrossem9ebd0942016-02-22 10:13:05 +010083
peustermde14f332016-03-15 16:14:21 +010084 # add the specified controller
peustermef6629e2016-03-14 17:21:56 +010085 self.addController('c0', controller=controller)
86
87 # graph of the complete DC network
stevenvanrossemc1149022016-04-11 01:16:44 +020088 self.DCNetwork_graph = nx.MultiDiGraph()
peustermef6629e2016-03-14 17:21:56 +010089
stevenvanrossem461941c2016-05-10 11:41:29 +020090 # initialize pool of vlan tags to setup the SDN paths
91 self.vlans = range(4096)[::-1]
92
stevenvanrossem27b6d952016-05-10 16:37:57 +020093 # link to Ryu REST_API
94 ryu_ip = '0.0.0.0'
95 ryu_port = '8080'
96 self.ryu_REST_api = 'http://{0}:{1}'.format(ryu_ip, ryu_port)
97
peustermef6629e2016-03-14 17:21:56 +010098 # monitoring agent
stevenvanrossem60670da2016-04-15 15:31:28 +020099 if monitor:
100 self.monitor_agent = DCNetworkMonitor(self)
101 else:
102 self.monitor_agent = None
peustermef6629e2016-03-14 17:21:56 +0100103
peusterm42f08be2016-03-10 21:56:34 +0100104 # initialize resource model registrar
peusterma4d84792016-03-25 12:27:07 +0100105 self.rm_registrar = ResourceModelRegistrar(
106 dc_emulation_max_cpu, dc_emulation_max_mem)
peustermcbcd4c22015-12-28 11:33:42 +0100107
peusterm60bf8b82016-04-06 14:12:35 +0200108 def addDatacenter(self, label, metadata={}, resource_log_path=None):
peustermcbcd4c22015-12-28 11:33:42 +0100109 """
110 Create and add a logical cloud data center to the network.
111 """
peusterma47db032016-02-04 14:55:29 +0100112 if label in self.dcs:
113 raise Exception("Data center label already exists: %s" % label)
peusterm60bf8b82016-04-06 14:12:35 +0200114 dc = Datacenter(label, metadata=metadata, resource_log_path=resource_log_path)
peustermcbcd4c22015-12-28 11:33:42 +0100115 dc.net = self # set reference to network
peusterma47db032016-02-04 14:55:29 +0100116 self.dcs[label] = dc
peustermcbcd4c22015-12-28 11:33:42 +0100117 dc.create() # finally create the data center in our Mininet instance
peusterma47db032016-02-04 14:55:29 +0100118 logging.info("added data center: %s" % label)
peustermcbcd4c22015-12-28 11:33:42 +0100119 return dc
120
peusterme6092692016-01-11 16:32:58 +0100121 def addLink(self, node1, node2, **params):
peusterm5b844a12016-01-11 15:58:15 +0100122 """
123 Able to handle Datacenter objects as link
124 end points.
125 """
peustermcbcd4c22015-12-28 11:33:42 +0100126 assert node1 is not None
127 assert node2 is not None
peusterme6092692016-01-11 16:32:58 +0100128 logging.debug("addLink: n1=%s n2=%s" % (str(node1), str(node2)))
peustermcbcd4c22015-12-28 11:33:42 +0100129 # ensure type of node1
130 if isinstance( node1, basestring ):
131 if node1 in self.dcs:
132 node1 = self.dcs[node1].switch
peustermcbcd4c22015-12-28 11:33:42 +0100133 if isinstance( node1, Datacenter ):
134 node1 = node1.switch
135 # ensure type of node2
136 if isinstance( node2, basestring ):
137 if node2 in self.dcs:
138 node2 = self.dcs[node2].switch
peustermcbcd4c22015-12-28 11:33:42 +0100139 if isinstance( node2, Datacenter ):
140 node2 = node2.switch
peustermc3b977e2016-01-12 10:09:35 +0100141 # try to give containers a default IP
142 if isinstance( node1, Docker ):
peustermea8db832016-03-08 10:25:58 +0100143 if "params1" not in params:
peustermc3b977e2016-01-12 10:09:35 +0100144 params["params1"] = {}
peustermea8db832016-03-08 10:25:58 +0100145 if "ip" not in params["params1"]:
peustermc3b977e2016-01-12 10:09:35 +0100146 params["params1"]["ip"] = self.getNextIp()
147 if isinstance( node2, Docker ):
peustermea8db832016-03-08 10:25:58 +0100148 if "params2" not in params:
peustermc3b977e2016-01-12 10:09:35 +0100149 params["params2"] = {}
peustermea8db832016-03-08 10:25:58 +0100150 if "ip" not in params["params2"]:
peustermc3b977e2016-01-12 10:09:35 +0100151 params["params2"]["ip"] = self.getNextIp()
peustermea8db832016-03-08 10:25:58 +0100152 # ensure that we allow TCLinks between data centers
153 # TODO this is not optimal, we use cls=Link for containers and TCLink for data centers
peusterm5877ea22016-05-11 13:44:59 +0200154 # see Containernet issue: https://github.com/mpeuster/containernet/issues/3
peustermea8db832016-03-08 10:25:58 +0100155 if "cls" not in params:
156 params["cls"] = TCLink
peustermc3b977e2016-01-12 10:09:35 +0100157
peusterm5877ea22016-05-11 13:44:59 +0200158 link = Containernet.addLink(self, node1, node2, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100159
stevenvanrossemc1149022016-04-11 01:16:44 +0200160 # try to give container interfaces a default id
161 node1_port_id = node1.ports[link.intf1]
162 if isinstance(node1, Docker):
163 if "id" in params["params1"]:
164 node1_port_id = params["params1"]["id"]
stevenvanrossem5b376412016-05-04 15:34:49 +0200165 node1_port_name = link.intf1.name
stevenvanrossemc1149022016-04-11 01:16:44 +0200166
167 node2_port_id = node2.ports[link.intf2]
168 if isinstance(node2, Docker):
169 if "id" in params["params2"]:
170 node2_port_id = params["params2"]["id"]
stevenvanrossem5b376412016-05-04 15:34:49 +0200171 node2_port_name = link.intf2.name
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200172
173
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100174 # add edge and assigned port number to graph in both directions between node1 and node2
stevenvanrossemc1149022016-04-11 01:16:44 +0200175 # port_id: id given in descriptor (if available, otherwise same as port)
peusterm5877ea22016-05-11 13:44:59 +0200176 # port: portnumber assigned by Containernet
stevenvanrossemc1149022016-04-11 01:16:44 +0200177
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200178 attr_dict = {}
179 # possible weight metrics allowed by TClink class:
180 weight_metrics = ['bw', 'delay', 'jitter', 'loss']
181 edge_attributes = [p for p in params if p in weight_metrics]
182 for attr in edge_attributes:
183 # if delay: strip ms (need number as weight in graph)
184 match = re.search('([0-9]*\.?[0-9]+)', params[attr])
185 if match:
186 attr_number = match.group(1)
187 else:
188 attr_number = None
189 attr_dict[attr] = attr_number
190
191
stevenvanrossem5b376412016-05-04 15:34:49 +0200192 attr_dict2 = {'src_port_id': node1_port_id, 'src_port_nr': node1.ports[link.intf1],
193 'src_port_name': node1_port_name,
194 'dst_port_id': node2_port_id, 'dst_port_nr': node2.ports[link.intf2],
195 'dst_port_name': node2_port_name}
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200196 attr_dict2.update(attr_dict)
197 self.DCNetwork_graph.add_edge(node1.name, node2.name, attr_dict=attr_dict2)
198
stevenvanrossem5b376412016-05-04 15:34:49 +0200199 attr_dict2 = {'src_port_id': node2_port_id, 'src_port_nr': node2.ports[link.intf2],
200 'src_port_name': node2_port_name,
201 'dst_port_id': node1_port_id, 'dst_port_nr': node1.ports[link.intf1],
202 'dst_port_name': node1_port_name}
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200203 attr_dict2.update(attr_dict)
204 self.DCNetwork_graph.add_edge(node2.name, node1.name, attr_dict=attr_dict2)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100205
206 return link
peustermcbcd4c22015-12-28 11:33:42 +0100207
peusterma47db032016-02-04 14:55:29 +0100208 def addDocker( self, label, **params ):
peusterm5b844a12016-01-11 15:58:15 +0100209 """
peusterm293cbc32016-01-13 17:05:28 +0100210 Wrapper for addDocker method to use custom container class.
peusterm5b844a12016-01-11 15:58:15 +0100211 """
stevenvanrossem8fbf9782016-02-17 11:40:23 +0100212 self.DCNetwork_graph.add_node(label)
peusterm5877ea22016-05-11 13:44:59 +0200213 return Containernet.addDocker(self, label, cls=EmulatorCompute, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100214
stevenvanrossem8fbf9782016-02-17 11:40:23 +0100215 def removeDocker( self, label, **params ):
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100216 """
217 Wrapper for removeDocker method to update graph.
218 """
stevenvanrossem8fbf9782016-02-17 11:40:23 +0100219 self.DCNetwork_graph.remove_node(label)
peusterm5877ea22016-05-11 13:44:59 +0200220 return Containernet.removeDocker(self, label, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100221
222 def addSwitch( self, name, add_to_graph=True, **params ):
223 """
224 Wrapper for addSwitch method to store switch also in graph.
225 """
226 if add_to_graph:
227 self.DCNetwork_graph.add_node(name)
peusterm5877ea22016-05-11 13:44:59 +0200228 return Containernet.addSwitch(self, name, protocols='OpenFlow10,OpenFlow12,OpenFlow13', **params)
peustermc3b977e2016-01-12 10:09:35 +0100229
peustermbd44f4a2016-01-13 14:53:30 +0100230 def getAllContainers(self):
231 """
232 Returns a list with all containers within all data centers.
233 """
234 all_containers = []
235 for dc in self.dcs.itervalues():
236 all_containers += dc.listCompute()
237 return all_containers
238
peustermcbcd4c22015-12-28 11:33:42 +0100239 def start(self):
240 # start
241 for dc in self.dcs.itervalues():
242 dc.start()
peusterm5877ea22016-05-11 13:44:59 +0200243 Containernet.start(self)
peustermcbcd4c22015-12-28 11:33:42 +0100244
245 def stop(self):
stevenvanrossem60670da2016-04-15 15:31:28 +0200246
stevenvanrossemc6abf132016-04-14 11:15:58 +0200247 # stop the monitor agent
stevenvanrossem60670da2016-04-15 15:31:28 +0200248 if self.monitor_agent is not None:
249 self.monitor_agent.stop()
peustermcbcd4c22015-12-28 11:33:42 +0100250
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200251 # stop emulator net
peusterm5877ea22016-05-11 13:44:59 +0200252 Containernet.stop(self)
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200253
254 # stop Ryu controller
255 self.stopRyu()
256
257
peustermcbcd4c22015-12-28 11:33:42 +0100258 def CLI(self):
peusterm293cbc32016-01-13 17:05:28 +0100259 CLI(self)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100260
261 # to remove chain do setChain( src, dst, cmd='del-flows')
stevenvanrossem461941c2016-05-10 11:41:29 +0200262 def setChain(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
263 cmd = kwargs.get('cmd')
264 if cmd == 'add-flow':
265 ret = self._chainAddFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface, **kwargs)
266 if kwargs.get('bidirectional'):
stevenvanrossem81955a52016-05-12 14:34:12 +0200267 ret = ret +'\n' + self._chainAddFlow(vnf_dst_name, vnf_src_name, vnf_dst_interface, vnf_src_interface, **kwargs)
stevenvanrossem9315da42016-04-11 12:10:06 +0200268
stevenvanrosseme131bf52016-07-14 11:42:09 +0200269 elif cmd == 'del-flows':
stevenvanrossem461941c2016-05-10 11:41:29 +0200270 ret = self._chainAddFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface, **kwargs)
271 if kwargs.get('bidirectional'):
stevenvanrossem81955a52016-05-12 14:34:12 +0200272 ret = ret + '\n' + self._chainAddFlow(vnf_dst_name, vnf_src_name, vnf_dst_interface, vnf_src_interface, **kwargs)
stevenvanrossem461941c2016-05-10 11:41:29 +0200273
274 else:
stevenvanrossem81955a52016-05-12 14:34:12 +0200275 ret = "Command unknown"
276
277 return ret
stevenvanrossem461941c2016-05-10 11:41:29 +0200278
279
280 def _chainAddFlow(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
281
stevenvanrossem9315da42016-04-11 12:10:06 +0200282 #check if port is specified (vnf:port)
stevenvanrossemed711fd2016-04-11 16:59:29 +0200283 if vnf_src_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200284 # take first interface by default
285 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
286 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200287 vnf_src_interface = link_dict[0]['src_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200288
stevenvanrossem9315da42016-04-11 12:10:06 +0200289 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
290 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
291 for link in link_dict:
stevenvanrossemed711fd2016-04-11 16:59:29 +0200292 if link_dict[link]['src_port_id'] == vnf_src_interface:
stevenvanrossem9315da42016-04-11 12:10:06 +0200293 # found the right link and connected switch
stevenvanrossem9315da42016-04-11 12:10:06 +0200294 src_sw = connected_sw
295
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200296 src_sw_inport_nr = link_dict[link]['dst_port_nr']
stevenvanrossem9315da42016-04-11 12:10:06 +0200297 break
298
stevenvanrossemed711fd2016-04-11 16:59:29 +0200299 if vnf_dst_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200300 # take first interface by default
301 connected_sw = self.DCNetwork_graph.neighbors(vnf_dst_name)[0]
302 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200303 vnf_dst_interface = link_dict[0]['dst_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200304
305 vnf_dst_name = vnf_dst_name.split(':')[0]
306 for connected_sw in self.DCNetwork_graph.neighbors(vnf_dst_name):
307 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
308 for link in link_dict:
stevenvanrossemed711fd2016-04-11 16:59:29 +0200309 if link_dict[link]['dst_port_id'] == vnf_dst_interface:
stevenvanrossem9315da42016-04-11 12:10:06 +0200310 # found the right link and connected switch
311 dst_sw = connected_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200312 dst_sw_outport_nr = link_dict[link]['src_port_nr']
stevenvanrossem9315da42016-04-11 12:10:06 +0200313 break
314
315
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100316 # get shortest path
stevenvanrossem9315da42016-04-11 12:10:06 +0200317 try:
stevenvanrossem1f68afb2016-05-02 15:36:04 +0200318 # returns the first found shortest path
319 # if all shortest paths are wanted, use: all_shortest_paths
stevenvanrossem461941c2016-05-10 11:41:29 +0200320 path = nx.shortest_path(self.DCNetwork_graph, src_sw, dst_sw, weight=kwargs.get('weight'))
stevenvanrossem9315da42016-04-11 12:10:06 +0200321 except:
322 logging.info("No path could be found between {0} and {1}".format(vnf_src_name, vnf_dst_name))
323 return "No path could be found between {0} and {1}".format(vnf_src_name, vnf_dst_name)
324
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100325 logging.info("Path between {0} and {1}: {2}".format(vnf_src_name, vnf_dst_name, path))
326
stevenvanrossem9315da42016-04-11 12:10:06 +0200327 current_hop = src_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200328 switch_inport_nr = src_sw_inport_nr
stevenvanrossem9315da42016-04-11 12:10:06 +0200329
stevenvanrossem461941c2016-05-10 11:41:29 +0200330 # choose free vlan if path contains more than 1 switch
stevenvanrossem27b6d952016-05-10 16:37:57 +0200331 cmd = kwargs.get('cmd')
332 vlan = None
333 if cmd == 'add-flow':
334 if len(path) > 1:
335 vlan = self.vlans.pop()
stevenvanrossem461941c2016-05-10 11:41:29 +0200336
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100337 for i in range(0,len(path)):
stevenvanrossem9315da42016-04-11 12:10:06 +0200338 current_node = self.getNodeByName(current_hop)
stevenvanrossem461941c2016-05-10 11:41:29 +0200339
stevenvanrossem9315da42016-04-11 12:10:06 +0200340 if path.index(current_hop) < len(path)-1:
341 next_hop = path[path.index(current_hop)+1]
342 else:
343 #last switch reached
344 next_hop = vnf_dst_name
345
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100346 next_node = self.getNodeByName(next_hop)
347
348 if next_hop == vnf_dst_name:
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200349 switch_outport_nr = dst_sw_outport_nr
stevenvanrossem9315da42016-04-11 12:10:06 +0200350 logging.info("end node reached: {0}".format(vnf_dst_name))
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100351 elif not isinstance( next_node, OVSSwitch ):
stevenvanrossemeefea6c2016-02-17 12:03:26 +0100352 logging.info("Next node: {0} is not a switch".format(next_hop))
353 return "Next node: {0} is not a switch".format(next_hop)
stevenvanrossem9315da42016-04-11 12:10:06 +0200354 else:
stevenvanrossemed711fd2016-04-11 16:59:29 +0200355 # take first link between switches by default
stevenvanrossem9315da42016-04-11 12:10:06 +0200356 index_edge_out = 0
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200357 switch_outport_nr = self.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
stevenvanrossem9315da42016-04-11 12:10:06 +0200358
stevenvanrossem9315da42016-04-11 12:10:06 +0200359
stevenvanrossem461941c2016-05-10 11:41:29 +0200360 # set of entry via ovs-ofctl
stevenvanrossem9315da42016-04-11 12:10:06 +0200361 if isinstance( current_node, OVSSwitch ):
stevenvanrossem461941c2016-05-10 11:41:29 +0200362 kwargs['vlan'] = vlan
363 kwargs['path'] = path
364 kwargs['current_hop'] = current_hop
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200365
366 if self.controller == RemoteController:
367 ## set flow entry via ryu rest api
368 self._set_flow_entry_ryu_rest(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
369 else:
370 ## set flow entry via ovs-ofctl
371 self._set_flow_entry_dpctl(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
372
373
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100374
stevenvanrossemed711fd2016-04-11 16:59:29 +0200375 # take first link between switches by default
376 if isinstance( next_node, OVSSwitch ):
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200377 switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
stevenvanrossemed711fd2016-04-11 16:59:29 +0200378 current_hop = next_hop
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100379
stevenvanrossem27b6d952016-05-10 16:37:57 +0200380 return "path {2} between {0} and {1}".format(vnf_src_name, vnf_dst_name, cmd)
381
382 def _set_flow_entry_ryu_rest(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
383 match = 'in_port=%s' % switch_inport_nr
384
385 cookie = kwargs.get('cookie')
386 match_input = kwargs.get('match')
387 cmd = kwargs.get('cmd')
388 path = kwargs.get('path')
389 current_hop = kwargs.get('current_hop')
390 vlan = kwargs.get('vlan')
391
392 s = ','
393 if match_input:
394 match = s.join([match, match_input])
395
396 flow = {}
397 flow['dpid'] = int(node.dpid, 16)
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200398
stevenvanrossem27b6d952016-05-10 16:37:57 +0200399 if cookie:
400 flow['cookie'] = int(cookie)
401
402
403 flow['actions'] = []
404
405 # possible Ryu actions, match fields:
406 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#add-a-flow-entry
407 if cmd == 'add-flow':
408 prefix = 'stats/flowentry/add'
stevenvanrossem27b6d952016-05-10 16:37:57 +0200409 if vlan != None:
410 if path.index(current_hop) == 0: # first node
411 action = {}
412 action['type'] = 'PUSH_VLAN' # Push a new VLAN tag if a input frame is non-VLAN-tagged
413 action['ethertype'] = 33024 # Ethertype 0x8100(=33024): IEEE 802.1Q VLAN-tagged frame
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200414 flow['actions'].append(action)
415 action = {}
stevenvanrossem27b6d952016-05-10 16:37:57 +0200416 action['type'] = 'SET_FIELD'
417 action['field'] = 'vlan_vid'
418 action['value'] = vlan
419 flow['actions'].append(action)
420 elif path.index(current_hop) == len(path) - 1: # last node
421 match += ',dl_vlan=%s' % vlan
422 action = {}
423 action['type'] = 'POP_VLAN'
424 flow['actions'].append(action)
425 else: # middle nodes
426 match += ',dl_vlan=%s' % vlan
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200427 # output action must come last
428 action = {}
429 action['type'] = 'OUTPUT'
430 action['port'] = switch_outport_nr
431 flow['actions'].append(action)
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200432
stevenvanrossem27b6d952016-05-10 16:37:57 +0200433 elif cmd == 'del-flows':
stevenvanrossem27b6d952016-05-10 16:37:57 +0200434 prefix = 'stats/flowentry/delete'
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200435
stevenvanrossem27b6d952016-05-10 16:37:57 +0200436 if cookie:
stevenvanrossem1ef77022016-05-12 16:36:10 +0200437 # TODO: add cookie_mask as argument
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200438 flow['cookie_mask'] = int('0xffffffffffffffff', 16) # need full mask to match complete cookie
stevenvanrossem27b6d952016-05-10 16:37:57 +0200439
440 action = {}
441 action['type'] = 'OUTPUT'
442 action['port'] = switch_outport_nr
443 flow['actions'].append(action)
444
445 flow['match'] = self._parse_match(match)
446 self.ryu_REST(prefix, data=flow)
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100447
stevenvanrossem461941c2016-05-10 11:41:29 +0200448 def _set_flow_entry_dpctl(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
stevenvanrossem23c48092016-05-06 17:21:12 +0200449 match = 'in_port=%s' % switch_inport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200450
451 cookie = kwargs.get('cookie')
452 match_input = kwargs.get('match')
453 cmd = kwargs.get('cmd')
454 path = kwargs.get('path')
455 current_hop = kwargs.get('current_hop')
456 vlan = kwargs.get('vlan')
457
stevenvanrossem898a2af2016-05-06 18:28:57 +0200458 s = ','
459 if cookie:
460 cookie = 'cookie=%s' % cookie
461 match = s.join([cookie, match])
stevenvanrossem23c48092016-05-06 17:21:12 +0200462 if match_input:
stevenvanrossem23c48092016-05-06 17:21:12 +0200463 match = s.join([match, match_input])
stevenvanrossem23c48092016-05-06 17:21:12 +0200464 if cmd == 'add-flow':
465 action = 'action=%s' % switch_outport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200466 if vlan != None:
467 if path.index(current_hop) == 0: # first node
468 action = ('action=mod_vlan_vid:%s' % vlan) + (',output=%s' % switch_outport_nr)
469 match = '-O OpenFlow13 ' + match
470 elif path.index(current_hop) == len(path) - 1: # last node
471 match += ',dl_vlan=%s' % vlan
472 action = 'action=strip_vlan,output=%s' % switch_outport_nr
473 else: # middle nodes
474 match += ',dl_vlan=%s' % vlan
stevenvanrossem23c48092016-05-06 17:21:12 +0200475 ofcmd = s.join([match, action])
476 elif cmd == 'del-flows':
477 ofcmd = match
478 else:
479 ofcmd = ''
480
481 node.dpctl(cmd, ofcmd)
stevenvanrossem461941c2016-05-10 11:41:29 +0200482 logging.info("{3} in switch: {0} in_port: {1} out_port: {2}".format(node.name, switch_inport_nr,
483 switch_outport_nr, cmd))
stevenvanrossem23c48092016-05-06 17:21:12 +0200484
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100485 # start Ryu Openflow controller as Remote Controller for the DCNetwork
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200486 def startRyu(self, learning_switch=True):
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100487 # start Ryu controller with rest-API
488 python_install_path = site.getsitepackages()[0]
489 ryu_path = python_install_path + '/ryu/app/simple_switch_13.py'
peustermde14f332016-03-15 16:14:21 +0100490 ryu_path2 = python_install_path + '/ryu/app/ofctl_rest.py'
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100491 # change the default Openflow controller port to 6653 (official IANA-assigned port number), as used by Mininet
492 # Ryu still uses 6633 as default
493 ryu_option = '--ofp-tcp-listen-port'
494 ryu_of_port = '6653'
peustermde14f332016-03-15 16:14:21 +0100495 ryu_cmd = 'ryu-manager'
peustermef6629e2016-03-14 17:21:56 +0100496 FNULL = open("/tmp/ryu.log", 'w')
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200497 if learning_switch:
498 self.ryu_process = Popen([ryu_cmd, ryu_path, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
499 else:
stevenvanrossem73efd192016-06-29 01:44:07 +0200500 # no learning switch, but with rest api
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200501 self.ryu_process = Popen([ryu_cmd, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
peusterm391773a2016-03-14 17:40:43 +0100502 time.sleep(1)
503
504 def stopRyu(self):
peustermde14f332016-03-15 16:14:21 +0100505 if self.ryu_process is not None:
peusterm391773a2016-03-14 17:40:43 +0100506 self.ryu_process.terminate()
507 self.ryu_process.kill()
stevenvanrossem89706802016-07-19 02:54:45 +0200508 self.killRyu()
509
510 @staticmethod
511 def removeLeftoverContainers():
512 # TODO can be more python-based using eg. docker-py?
513 Popen('docker ps -a -q --filter="name=mn.*" | xargs -r docker rm -f', shell=True)
514
515 @staticmethod
516 def killRyu():
517 Popen(['pkill', '-f', 'ryu-manager'])
peusterm391773a2016-03-14 17:40:43 +0100518
stevenvanrossem27b6d952016-05-10 16:37:57 +0200519 def ryu_REST(self, prefix, dpid=None, data=None):
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200520 try:
521 if dpid:
522 url = self.ryu_REST_api + '/' + str(prefix) + '/' + str(dpid)
523 else:
524 url = self.ryu_REST_api + '/' + str(prefix)
525 if data:
526 #logging.info('POST: {0}'.format(str(data)))
527 req = urllib2.Request(url, str(data))
528 else:
529 req = urllib2.Request(url)
stevenvanrossem27b6d952016-05-10 16:37:57 +0200530
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200531 ret = urllib2.urlopen(req).read()
532 return ret
533 except:
534 logging.info('error url: {0}'.format(str(url)))
535 if data: logging.info('error POST: {0}'.format(str(data)))
stevenvanrossem27b6d952016-05-10 16:37:57 +0200536
537 # need to respect that some match fields must be integers
538 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#description-of-match-and-actions
539 def _parse_match(self, match):
540 matches = match.split(',')
541 dict = {}
542 for m in matches:
543 match = m.split('=')
544 if len(match) == 2:
545 try:
546 m2 = int(match[1], 0)
547 except:
548 m2 = match[1]
549
550 dict.update({match[0]:m2})
551 return dict
552