blob: adf17d4a3fa0778e9d1a9774833d57e83fef7842 [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
peusterm293cbc32016-01-13 17:05:28 +010068 # call original Docker.__init__ and setup default controller
peusterm5877ea22016-05-11 13:44:59 +020069 Containernet.__init__(
stevenvanrossem7cd3c252016-05-11 22:55:15 +020070 self, switch=OVSKernelSwitch, controller=controller, **kwargs)
stevenvanrossemc5a536a2016-02-16 14:52:39 +010071
peusterm293cbc32016-01-13 17:05:28 +010072
peustermde14f332016-03-15 16:14:21 +010073 # Ryu management
74 self.ryu_process = None
75 if controller == RemoteController:
76 # start Ryu controller
stevenvanrossem7cd3c252016-05-11 22:55:15 +020077 self.startRyu(learning_switch=enable_learning)
stevenvanrossem9ebd0942016-02-22 10:13:05 +010078
peustermde14f332016-03-15 16:14:21 +010079 # add the specified controller
peustermef6629e2016-03-14 17:21:56 +010080 self.addController('c0', controller=controller)
81
82 # graph of the complete DC network
stevenvanrossemc1149022016-04-11 01:16:44 +020083 self.DCNetwork_graph = nx.MultiDiGraph()
peustermef6629e2016-03-14 17:21:56 +010084
stevenvanrossem461941c2016-05-10 11:41:29 +020085 # initialize pool of vlan tags to setup the SDN paths
86 self.vlans = range(4096)[::-1]
87
stevenvanrossem27b6d952016-05-10 16:37:57 +020088 # link to Ryu REST_API
89 ryu_ip = '0.0.0.0'
90 ryu_port = '8080'
91 self.ryu_REST_api = 'http://{0}:{1}'.format(ryu_ip, ryu_port)
92
peustermef6629e2016-03-14 17:21:56 +010093 # monitoring agent
stevenvanrossem60670da2016-04-15 15:31:28 +020094 if monitor:
95 self.monitor_agent = DCNetworkMonitor(self)
96 else:
97 self.monitor_agent = None
peustermef6629e2016-03-14 17:21:56 +010098
peusterm42f08be2016-03-10 21:56:34 +010099 # initialize resource model registrar
peusterma4d84792016-03-25 12:27:07 +0100100 self.rm_registrar = ResourceModelRegistrar(
101 dc_emulation_max_cpu, dc_emulation_max_mem)
peustermcbcd4c22015-12-28 11:33:42 +0100102
peusterm60bf8b82016-04-06 14:12:35 +0200103 def addDatacenter(self, label, metadata={}, resource_log_path=None):
peustermcbcd4c22015-12-28 11:33:42 +0100104 """
105 Create and add a logical cloud data center to the network.
106 """
peusterma47db032016-02-04 14:55:29 +0100107 if label in self.dcs:
108 raise Exception("Data center label already exists: %s" % label)
peusterm60bf8b82016-04-06 14:12:35 +0200109 dc = Datacenter(label, metadata=metadata, resource_log_path=resource_log_path)
peustermcbcd4c22015-12-28 11:33:42 +0100110 dc.net = self # set reference to network
peusterma47db032016-02-04 14:55:29 +0100111 self.dcs[label] = dc
peustermcbcd4c22015-12-28 11:33:42 +0100112 dc.create() # finally create the data center in our Mininet instance
peusterma47db032016-02-04 14:55:29 +0100113 logging.info("added data center: %s" % label)
peustermcbcd4c22015-12-28 11:33:42 +0100114 return dc
115
peusterme6092692016-01-11 16:32:58 +0100116 def addLink(self, node1, node2, **params):
peusterm5b844a12016-01-11 15:58:15 +0100117 """
118 Able to handle Datacenter objects as link
119 end points.
120 """
peustermcbcd4c22015-12-28 11:33:42 +0100121 assert node1 is not None
122 assert node2 is not None
peusterme6092692016-01-11 16:32:58 +0100123 logging.debug("addLink: n1=%s n2=%s" % (str(node1), str(node2)))
peustermcbcd4c22015-12-28 11:33:42 +0100124 # ensure type of node1
125 if isinstance( node1, basestring ):
126 if node1 in self.dcs:
127 node1 = self.dcs[node1].switch
peustermcbcd4c22015-12-28 11:33:42 +0100128 if isinstance( node1, Datacenter ):
129 node1 = node1.switch
130 # ensure type of node2
131 if isinstance( node2, basestring ):
132 if node2 in self.dcs:
133 node2 = self.dcs[node2].switch
peustermcbcd4c22015-12-28 11:33:42 +0100134 if isinstance( node2, Datacenter ):
135 node2 = node2.switch
peustermc3b977e2016-01-12 10:09:35 +0100136 # try to give containers a default IP
137 if isinstance( node1, Docker ):
peustermea8db832016-03-08 10:25:58 +0100138 if "params1" not in params:
peustermc3b977e2016-01-12 10:09:35 +0100139 params["params1"] = {}
peustermea8db832016-03-08 10:25:58 +0100140 if "ip" not in params["params1"]:
peustermc3b977e2016-01-12 10:09:35 +0100141 params["params1"]["ip"] = self.getNextIp()
142 if isinstance( node2, Docker ):
peustermea8db832016-03-08 10:25:58 +0100143 if "params2" not in params:
peustermc3b977e2016-01-12 10:09:35 +0100144 params["params2"] = {}
peustermea8db832016-03-08 10:25:58 +0100145 if "ip" not in params["params2"]:
peustermc3b977e2016-01-12 10:09:35 +0100146 params["params2"]["ip"] = self.getNextIp()
peustermea8db832016-03-08 10:25:58 +0100147 # ensure that we allow TCLinks between data centers
148 # TODO this is not optimal, we use cls=Link for containers and TCLink for data centers
peusterm5877ea22016-05-11 13:44:59 +0200149 # see Containernet issue: https://github.com/mpeuster/containernet/issues/3
peustermea8db832016-03-08 10:25:58 +0100150 if "cls" not in params:
151 params["cls"] = TCLink
peustermc3b977e2016-01-12 10:09:35 +0100152
peusterm5877ea22016-05-11 13:44:59 +0200153 link = Containernet.addLink(self, node1, node2, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100154
stevenvanrossemc1149022016-04-11 01:16:44 +0200155 # try to give container interfaces a default id
156 node1_port_id = node1.ports[link.intf1]
157 if isinstance(node1, Docker):
158 if "id" in params["params1"]:
159 node1_port_id = params["params1"]["id"]
stevenvanrossem5b376412016-05-04 15:34:49 +0200160 node1_port_name = link.intf1.name
stevenvanrossemc1149022016-04-11 01:16:44 +0200161
162 node2_port_id = node2.ports[link.intf2]
163 if isinstance(node2, Docker):
164 if "id" in params["params2"]:
165 node2_port_id = params["params2"]["id"]
stevenvanrossem5b376412016-05-04 15:34:49 +0200166 node2_port_name = link.intf2.name
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200167
168
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100169 # add edge and assigned port number to graph in both directions between node1 and node2
stevenvanrossemc1149022016-04-11 01:16:44 +0200170 # port_id: id given in descriptor (if available, otherwise same as port)
peusterm5877ea22016-05-11 13:44:59 +0200171 # port: portnumber assigned by Containernet
stevenvanrossemc1149022016-04-11 01:16:44 +0200172
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200173 attr_dict = {}
174 # possible weight metrics allowed by TClink class:
175 weight_metrics = ['bw', 'delay', 'jitter', 'loss']
176 edge_attributes = [p for p in params if p in weight_metrics]
177 for attr in edge_attributes:
178 # if delay: strip ms (need number as weight in graph)
179 match = re.search('([0-9]*\.?[0-9]+)', params[attr])
180 if match:
181 attr_number = match.group(1)
182 else:
183 attr_number = None
184 attr_dict[attr] = attr_number
185
186
stevenvanrossem5b376412016-05-04 15:34:49 +0200187 attr_dict2 = {'src_port_id': node1_port_id, 'src_port_nr': node1.ports[link.intf1],
188 'src_port_name': node1_port_name,
189 'dst_port_id': node2_port_id, 'dst_port_nr': node2.ports[link.intf2],
190 'dst_port_name': node2_port_name}
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200191 attr_dict2.update(attr_dict)
192 self.DCNetwork_graph.add_edge(node1.name, node2.name, attr_dict=attr_dict2)
193
stevenvanrossem5b376412016-05-04 15:34:49 +0200194 attr_dict2 = {'src_port_id': node2_port_id, 'src_port_nr': node2.ports[link.intf2],
195 'src_port_name': node2_port_name,
196 'dst_port_id': node1_port_id, 'dst_port_nr': node1.ports[link.intf1],
197 'dst_port_name': node1_port_name}
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200198 attr_dict2.update(attr_dict)
199 self.DCNetwork_graph.add_edge(node2.name, node1.name, attr_dict=attr_dict2)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100200
201 return link
peustermcbcd4c22015-12-28 11:33:42 +0100202
peusterma47db032016-02-04 14:55:29 +0100203 def addDocker( self, label, **params ):
peusterm5b844a12016-01-11 15:58:15 +0100204 """
peusterm293cbc32016-01-13 17:05:28 +0100205 Wrapper for addDocker method to use custom container class.
peusterm5b844a12016-01-11 15:58:15 +0100206 """
stevenvanrossem8fbf9782016-02-17 11:40:23 +0100207 self.DCNetwork_graph.add_node(label)
peusterm5877ea22016-05-11 13:44:59 +0200208 return Containernet.addDocker(self, label, cls=EmulatorCompute, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100209
stevenvanrossem8fbf9782016-02-17 11:40:23 +0100210 def removeDocker( self, label, **params ):
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100211 """
212 Wrapper for removeDocker method to update graph.
213 """
stevenvanrossem8fbf9782016-02-17 11:40:23 +0100214 self.DCNetwork_graph.remove_node(label)
peusterm5877ea22016-05-11 13:44:59 +0200215 return Containernet.removeDocker(self, label, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100216
217 def addSwitch( self, name, add_to_graph=True, **params ):
218 """
219 Wrapper for addSwitch method to store switch also in graph.
220 """
221 if add_to_graph:
222 self.DCNetwork_graph.add_node(name)
peusterm5877ea22016-05-11 13:44:59 +0200223 return Containernet.addSwitch(self, name, protocols='OpenFlow10,OpenFlow12,OpenFlow13', **params)
peustermc3b977e2016-01-12 10:09:35 +0100224
peustermbd44f4a2016-01-13 14:53:30 +0100225 def getAllContainers(self):
226 """
227 Returns a list with all containers within all data centers.
228 """
229 all_containers = []
230 for dc in self.dcs.itervalues():
231 all_containers += dc.listCompute()
232 return all_containers
233
peustermcbcd4c22015-12-28 11:33:42 +0100234 def start(self):
235 # start
236 for dc in self.dcs.itervalues():
237 dc.start()
peusterm5877ea22016-05-11 13:44:59 +0200238 Containernet.start(self)
peustermcbcd4c22015-12-28 11:33:42 +0100239
240 def stop(self):
stevenvanrossem60670da2016-04-15 15:31:28 +0200241
stevenvanrossemc6abf132016-04-14 11:15:58 +0200242 # stop the monitor agent
stevenvanrossem60670da2016-04-15 15:31:28 +0200243 if self.monitor_agent is not None:
244 self.monitor_agent.stop()
peustermcbcd4c22015-12-28 11:33:42 +0100245
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200246 # stop emulator net
peusterm5877ea22016-05-11 13:44:59 +0200247 Containernet.stop(self)
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200248
249 # stop Ryu controller
250 self.stopRyu()
251
252
peustermcbcd4c22015-12-28 11:33:42 +0100253 def CLI(self):
peusterm293cbc32016-01-13 17:05:28 +0100254 CLI(self)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100255
256 # to remove chain do setChain( src, dst, cmd='del-flows')
stevenvanrossem461941c2016-05-10 11:41:29 +0200257 def setChain(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
258 cmd = kwargs.get('cmd')
259 if cmd == 'add-flow':
260 ret = self._chainAddFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface, **kwargs)
261 if kwargs.get('bidirectional'):
stevenvanrossem81955a52016-05-12 14:34:12 +0200262 ret = ret +'\n' + self._chainAddFlow(vnf_dst_name, vnf_src_name, vnf_dst_interface, vnf_src_interface, **kwargs)
stevenvanrossem9315da42016-04-11 12:10:06 +0200263
stevenvanrosseme131bf52016-07-14 11:42:09 +0200264 elif cmd == 'del-flows':
stevenvanrossem461941c2016-05-10 11:41:29 +0200265 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)
stevenvanrossem461941c2016-05-10 11:41:29 +0200268
269 else:
stevenvanrossem81955a52016-05-12 14:34:12 +0200270 ret = "Command unknown"
271
272 return ret
stevenvanrossem461941c2016-05-10 11:41:29 +0200273
274
275 def _chainAddFlow(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
276
stevenvanrossem9315da42016-04-11 12:10:06 +0200277 #check if port is specified (vnf:port)
stevenvanrossemed711fd2016-04-11 16:59:29 +0200278 if vnf_src_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200279 # take first interface by default
280 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
281 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200282 vnf_src_interface = link_dict[0]['src_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200283
stevenvanrossem9315da42016-04-11 12:10:06 +0200284 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
285 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
286 for link in link_dict:
stevenvanrossemed711fd2016-04-11 16:59:29 +0200287 if link_dict[link]['src_port_id'] == vnf_src_interface:
stevenvanrossem9315da42016-04-11 12:10:06 +0200288 # found the right link and connected switch
stevenvanrossem9315da42016-04-11 12:10:06 +0200289 src_sw = connected_sw
290
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200291 src_sw_inport_nr = link_dict[link]['dst_port_nr']
stevenvanrossem9315da42016-04-11 12:10:06 +0200292 break
293
stevenvanrossemed711fd2016-04-11 16:59:29 +0200294 if vnf_dst_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200295 # take first interface by default
296 connected_sw = self.DCNetwork_graph.neighbors(vnf_dst_name)[0]
297 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200298 vnf_dst_interface = link_dict[0]['dst_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200299
300 vnf_dst_name = vnf_dst_name.split(':')[0]
301 for connected_sw in self.DCNetwork_graph.neighbors(vnf_dst_name):
302 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
303 for link in link_dict:
stevenvanrossemed711fd2016-04-11 16:59:29 +0200304 if link_dict[link]['dst_port_id'] == vnf_dst_interface:
stevenvanrossem9315da42016-04-11 12:10:06 +0200305 # found the right link and connected switch
306 dst_sw = connected_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200307 dst_sw_outport_nr = link_dict[link]['src_port_nr']
stevenvanrossem9315da42016-04-11 12:10:06 +0200308 break
309
310
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100311 # get shortest path
stevenvanrossem9315da42016-04-11 12:10:06 +0200312 try:
stevenvanrossem1f68afb2016-05-02 15:36:04 +0200313 # returns the first found shortest path
314 # if all shortest paths are wanted, use: all_shortest_paths
stevenvanrossem461941c2016-05-10 11:41:29 +0200315 path = nx.shortest_path(self.DCNetwork_graph, src_sw, dst_sw, weight=kwargs.get('weight'))
stevenvanrossem9315da42016-04-11 12:10:06 +0200316 except:
peusterma664eb62016-07-18 09:03:46 +0200317 logging.exception("No path could be found between {0} and {1}".format(vnf_src_name, vnf_dst_name))
318 logging.debug("Graph nodes: %r" % self.DCNetwork_graph.nodes())
319 logging.debug("Graph edges: %r" % self.DCNetwork_graph.edges())
stevenvanrossem9315da42016-04-11 12:10:06 +0200320 return "No path could be found between {0} and {1}".format(vnf_src_name, vnf_dst_name)
321
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100322 logging.info("Path between {0} and {1}: {2}".format(vnf_src_name, vnf_dst_name, path))
323
stevenvanrossem9315da42016-04-11 12:10:06 +0200324 current_hop = src_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200325 switch_inport_nr = src_sw_inport_nr
stevenvanrossem9315da42016-04-11 12:10:06 +0200326
stevenvanrossem461941c2016-05-10 11:41:29 +0200327 # choose free vlan if path contains more than 1 switch
stevenvanrossem27b6d952016-05-10 16:37:57 +0200328 cmd = kwargs.get('cmd')
329 vlan = None
330 if cmd == 'add-flow':
331 if len(path) > 1:
332 vlan = self.vlans.pop()
stevenvanrossem461941c2016-05-10 11:41:29 +0200333
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100334 for i in range(0,len(path)):
stevenvanrossem9315da42016-04-11 12:10:06 +0200335 current_node = self.getNodeByName(current_hop)
stevenvanrossem461941c2016-05-10 11:41:29 +0200336
stevenvanrossem9315da42016-04-11 12:10:06 +0200337 if path.index(current_hop) < len(path)-1:
338 next_hop = path[path.index(current_hop)+1]
339 else:
340 #last switch reached
341 next_hop = vnf_dst_name
342
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100343 next_node = self.getNodeByName(next_hop)
344
345 if next_hop == vnf_dst_name:
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200346 switch_outport_nr = dst_sw_outport_nr
stevenvanrossem9315da42016-04-11 12:10:06 +0200347 logging.info("end node reached: {0}".format(vnf_dst_name))
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100348 elif not isinstance( next_node, OVSSwitch ):
stevenvanrossemeefea6c2016-02-17 12:03:26 +0100349 logging.info("Next node: {0} is not a switch".format(next_hop))
350 return "Next node: {0} is not a switch".format(next_hop)
stevenvanrossem9315da42016-04-11 12:10:06 +0200351 else:
stevenvanrossemed711fd2016-04-11 16:59:29 +0200352 # take first link between switches by default
stevenvanrossem9315da42016-04-11 12:10:06 +0200353 index_edge_out = 0
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200354 switch_outport_nr = self.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
stevenvanrossem9315da42016-04-11 12:10:06 +0200355
stevenvanrossem9315da42016-04-11 12:10:06 +0200356
stevenvanrossem461941c2016-05-10 11:41:29 +0200357 # set of entry via ovs-ofctl
stevenvanrossem9315da42016-04-11 12:10:06 +0200358 if isinstance( current_node, OVSSwitch ):
stevenvanrossem461941c2016-05-10 11:41:29 +0200359 kwargs['vlan'] = vlan
360 kwargs['path'] = path
361 kwargs['current_hop'] = current_hop
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200362
363 if self.controller == RemoteController:
364 ## set flow entry via ryu rest api
365 self._set_flow_entry_ryu_rest(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
366 else:
367 ## set flow entry via ovs-ofctl
368 self._set_flow_entry_dpctl(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
369
370
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100371
stevenvanrossemed711fd2016-04-11 16:59:29 +0200372 # take first link between switches by default
373 if isinstance( next_node, OVSSwitch ):
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200374 switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
stevenvanrossemed711fd2016-04-11 16:59:29 +0200375 current_hop = next_hop
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100376
stevenvanrossem27b6d952016-05-10 16:37:57 +0200377 return "path {2} between {0} and {1}".format(vnf_src_name, vnf_dst_name, cmd)
378
379 def _set_flow_entry_ryu_rest(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
380 match = 'in_port=%s' % switch_inport_nr
381
382 cookie = kwargs.get('cookie')
383 match_input = kwargs.get('match')
384 cmd = kwargs.get('cmd')
385 path = kwargs.get('path')
386 current_hop = kwargs.get('current_hop')
387 vlan = kwargs.get('vlan')
388
389 s = ','
390 if match_input:
391 match = s.join([match, match_input])
392
393 flow = {}
394 flow['dpid'] = int(node.dpid, 16)
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200395
stevenvanrossem27b6d952016-05-10 16:37:57 +0200396 if cookie:
397 flow['cookie'] = int(cookie)
398
399
400 flow['actions'] = []
401
402 # possible Ryu actions, match fields:
403 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#add-a-flow-entry
404 if cmd == 'add-flow':
405 prefix = 'stats/flowentry/add'
stevenvanrossem27b6d952016-05-10 16:37:57 +0200406 if vlan != None:
407 if path.index(current_hop) == 0: # first node
408 action = {}
409 action['type'] = 'PUSH_VLAN' # Push a new VLAN tag if a input frame is non-VLAN-tagged
410 action['ethertype'] = 33024 # Ethertype 0x8100(=33024): IEEE 802.1Q VLAN-tagged frame
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200411 flow['actions'].append(action)
412 action = {}
stevenvanrossem27b6d952016-05-10 16:37:57 +0200413 action['type'] = 'SET_FIELD'
414 action['field'] = 'vlan_vid'
415 action['value'] = vlan
416 flow['actions'].append(action)
417 elif path.index(current_hop) == len(path) - 1: # last node
418 match += ',dl_vlan=%s' % vlan
419 action = {}
420 action['type'] = 'POP_VLAN'
421 flow['actions'].append(action)
422 else: # middle nodes
423 match += ',dl_vlan=%s' % vlan
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200424 # output action must come last
425 action = {}
426 action['type'] = 'OUTPUT'
427 action['port'] = switch_outport_nr
428 flow['actions'].append(action)
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200429
stevenvanrossem27b6d952016-05-10 16:37:57 +0200430 elif cmd == 'del-flows':
stevenvanrossem27b6d952016-05-10 16:37:57 +0200431 prefix = 'stats/flowentry/delete'
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200432
stevenvanrossem27b6d952016-05-10 16:37:57 +0200433 if cookie:
stevenvanrossem1ef77022016-05-12 16:36:10 +0200434 # TODO: add cookie_mask as argument
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200435 flow['cookie_mask'] = int('0xffffffffffffffff', 16) # need full mask to match complete cookie
stevenvanrossem27b6d952016-05-10 16:37:57 +0200436
437 action = {}
438 action['type'] = 'OUTPUT'
439 action['port'] = switch_outport_nr
440 flow['actions'].append(action)
441
442 flow['match'] = self._parse_match(match)
443 self.ryu_REST(prefix, data=flow)
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100444
stevenvanrossem461941c2016-05-10 11:41:29 +0200445 def _set_flow_entry_dpctl(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
stevenvanrossem23c48092016-05-06 17:21:12 +0200446 match = 'in_port=%s' % switch_inport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200447
448 cookie = kwargs.get('cookie')
449 match_input = kwargs.get('match')
450 cmd = kwargs.get('cmd')
451 path = kwargs.get('path')
452 current_hop = kwargs.get('current_hop')
453 vlan = kwargs.get('vlan')
454
stevenvanrossem898a2af2016-05-06 18:28:57 +0200455 s = ','
456 if cookie:
457 cookie = 'cookie=%s' % cookie
458 match = s.join([cookie, match])
stevenvanrossem23c48092016-05-06 17:21:12 +0200459 if match_input:
stevenvanrossem23c48092016-05-06 17:21:12 +0200460 match = s.join([match, match_input])
stevenvanrossem23c48092016-05-06 17:21:12 +0200461 if cmd == 'add-flow':
462 action = 'action=%s' % switch_outport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200463 if vlan != None:
464 if path.index(current_hop) == 0: # first node
465 action = ('action=mod_vlan_vid:%s' % vlan) + (',output=%s' % switch_outport_nr)
466 match = '-O OpenFlow13 ' + match
467 elif path.index(current_hop) == len(path) - 1: # last node
468 match += ',dl_vlan=%s' % vlan
469 action = 'action=strip_vlan,output=%s' % switch_outport_nr
470 else: # middle nodes
471 match += ',dl_vlan=%s' % vlan
stevenvanrossem23c48092016-05-06 17:21:12 +0200472 ofcmd = s.join([match, action])
473 elif cmd == 'del-flows':
474 ofcmd = match
475 else:
476 ofcmd = ''
477
478 node.dpctl(cmd, ofcmd)
stevenvanrossem461941c2016-05-10 11:41:29 +0200479 logging.info("{3} in switch: {0} in_port: {1} out_port: {2}".format(node.name, switch_inport_nr,
480 switch_outport_nr, cmd))
stevenvanrossem23c48092016-05-06 17:21:12 +0200481
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100482 # start Ryu Openflow controller as Remote Controller for the DCNetwork
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200483 def startRyu(self, learning_switch=True):
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100484 # start Ryu controller with rest-API
485 python_install_path = site.getsitepackages()[0]
486 ryu_path = python_install_path + '/ryu/app/simple_switch_13.py'
peustermde14f332016-03-15 16:14:21 +0100487 ryu_path2 = python_install_path + '/ryu/app/ofctl_rest.py'
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100488 # change the default Openflow controller port to 6653 (official IANA-assigned port number), as used by Mininet
489 # Ryu still uses 6633 as default
490 ryu_option = '--ofp-tcp-listen-port'
491 ryu_of_port = '6653'
peustermde14f332016-03-15 16:14:21 +0100492 ryu_cmd = 'ryu-manager'
peustermef6629e2016-03-14 17:21:56 +0100493 FNULL = open("/tmp/ryu.log", 'w')
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200494 if learning_switch:
495 self.ryu_process = Popen([ryu_cmd, ryu_path, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
496 else:
stevenvanrossem73efd192016-06-29 01:44:07 +0200497 # no learning switch, but with rest api
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200498 self.ryu_process = Popen([ryu_cmd, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
peusterm391773a2016-03-14 17:40:43 +0100499 time.sleep(1)
500
501 def stopRyu(self):
peustermde14f332016-03-15 16:14:21 +0100502 if self.ryu_process is not None:
peusterm391773a2016-03-14 17:40:43 +0100503 self.ryu_process.terminate()
504 self.ryu_process.kill()
505
stevenvanrossem27b6d952016-05-10 16:37:57 +0200506 def ryu_REST(self, prefix, dpid=None, data=None):
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200507 try:
508 if dpid:
509 url = self.ryu_REST_api + '/' + str(prefix) + '/' + str(dpid)
510 else:
511 url = self.ryu_REST_api + '/' + str(prefix)
512 if data:
513 #logging.info('POST: {0}'.format(str(data)))
514 req = urllib2.Request(url, str(data))
515 else:
516 req = urllib2.Request(url)
stevenvanrossem27b6d952016-05-10 16:37:57 +0200517
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200518 ret = urllib2.urlopen(req).read()
519 return ret
520 except:
521 logging.info('error url: {0}'.format(str(url)))
522 if data: logging.info('error POST: {0}'.format(str(data)))
stevenvanrossem27b6d952016-05-10 16:37:57 +0200523
524 # need to respect that some match fields must be integers
525 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#description-of-match-and-actions
526 def _parse_match(self, match):
527 matches = match.split(',')
528 dict = {}
529 for m in matches:
530 match = m.split('=')
531 if len(match) == 2:
532 try:
533 m2 = int(match[1], 0)
534 except:
535 m2 = match[1]
536
537 dict.update({match[0]:m2})
538 return dict
539