blob: ade984fbb54fa5183eaef295ba3e765830e363bf [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
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,
stevenvanrossem7cd3c252016-05-11 22:55:15 +020059 enable_learning = True, # in case of RemoteController (Ryu), learning switch behavior can be turned off/on
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 """
peustermcbcd4c22015-12-28 11:33:42 +010069 self.dcs = {}
peusterm42f08be2016-03-10 21:56:34 +010070
peusterm293cbc32016-01-13 17:05:28 +010071 # call original Docker.__init__ and setup default controller
peusterm5877ea22016-05-11 13:44:59 +020072 Containernet.__init__(
stevenvanrossem7cd3c252016-05-11 22:55:15 +020073 self, switch=OVSKernelSwitch, controller=controller, **kwargs)
stevenvanrossemc5a536a2016-02-16 14:52:39 +010074
peusterm293cbc32016-01-13 17:05:28 +010075
peustermde14f332016-03-15 16:14:21 +010076 # Ryu management
77 self.ryu_process = None
78 if controller == RemoteController:
79 # start Ryu controller
stevenvanrossem7cd3c252016-05-11 22:55:15 +020080 self.startRyu(learning_switch=enable_learning)
stevenvanrossem9ebd0942016-02-22 10:13:05 +010081
peustermde14f332016-03-15 16:14:21 +010082 # add the specified controller
peustermef6629e2016-03-14 17:21:56 +010083 self.addController('c0', controller=controller)
84
85 # graph of the complete DC network
stevenvanrossemc1149022016-04-11 01:16:44 +020086 self.DCNetwork_graph = nx.MultiDiGraph()
peustermef6629e2016-03-14 17:21:56 +010087
stevenvanrossem461941c2016-05-10 11:41:29 +020088 # initialize pool of vlan tags to setup the SDN paths
89 self.vlans = range(4096)[::-1]
90
stevenvanrossem27b6d952016-05-10 16:37:57 +020091 # link to Ryu REST_API
92 ryu_ip = '0.0.0.0'
93 ryu_port = '8080'
94 self.ryu_REST_api = 'http://{0}:{1}'.format(ryu_ip, ryu_port)
95
peustermef6629e2016-03-14 17:21:56 +010096 # monitoring agent
stevenvanrossem60670da2016-04-15 15:31:28 +020097 if monitor:
98 self.monitor_agent = DCNetworkMonitor(self)
99 else:
100 self.monitor_agent = None
peustermef6629e2016-03-14 17:21:56 +0100101
peusterm42f08be2016-03-10 21:56:34 +0100102 # initialize resource model registrar
peusterma4d84792016-03-25 12:27:07 +0100103 self.rm_registrar = ResourceModelRegistrar(
104 dc_emulation_max_cpu, dc_emulation_max_mem)
peustermcbcd4c22015-12-28 11:33:42 +0100105
peusterm60bf8b82016-04-06 14:12:35 +0200106 def addDatacenter(self, label, metadata={}, resource_log_path=None):
peustermcbcd4c22015-12-28 11:33:42 +0100107 """
108 Create and add a logical cloud data center to the network.
109 """
peusterma47db032016-02-04 14:55:29 +0100110 if label in self.dcs:
111 raise Exception("Data center label already exists: %s" % label)
peusterm60bf8b82016-04-06 14:12:35 +0200112 dc = Datacenter(label, metadata=metadata, resource_log_path=resource_log_path)
peustermcbcd4c22015-12-28 11:33:42 +0100113 dc.net = self # set reference to network
peusterma47db032016-02-04 14:55:29 +0100114 self.dcs[label] = dc
peustermcbcd4c22015-12-28 11:33:42 +0100115 dc.create() # finally create the data center in our Mininet instance
peustermf9a817d2016-07-18 09:06:04 +0200116 LOG.info("added data center: %s" % label)
peustermcbcd4c22015-12-28 11:33:42 +0100117 return dc
118
peusterme6092692016-01-11 16:32:58 +0100119 def addLink(self, node1, node2, **params):
peusterm5b844a12016-01-11 15:58:15 +0100120 """
121 Able to handle Datacenter objects as link
122 end points.
123 """
peustermcbcd4c22015-12-28 11:33:42 +0100124 assert node1 is not None
125 assert node2 is not None
peustermf9a817d2016-07-18 09:06:04 +0200126 LOG.debug("addLink: n1=%s n2=%s" % (str(node1), str(node2)))
peustermcbcd4c22015-12-28 11:33:42 +0100127 # ensure type of node1
128 if isinstance( node1, basestring ):
129 if node1 in self.dcs:
130 node1 = self.dcs[node1].switch
peustermcbcd4c22015-12-28 11:33:42 +0100131 if isinstance( node1, Datacenter ):
132 node1 = node1.switch
133 # ensure type of node2
134 if isinstance( node2, basestring ):
135 if node2 in self.dcs:
136 node2 = self.dcs[node2].switch
peustermcbcd4c22015-12-28 11:33:42 +0100137 if isinstance( node2, Datacenter ):
138 node2 = node2.switch
peustermc3b977e2016-01-12 10:09:35 +0100139 # try to give containers a default IP
140 if isinstance( node1, Docker ):
peustermea8db832016-03-08 10:25:58 +0100141 if "params1" not in params:
peustermc3b977e2016-01-12 10:09:35 +0100142 params["params1"] = {}
peustermea8db832016-03-08 10:25:58 +0100143 if "ip" not in params["params1"]:
peustermc3b977e2016-01-12 10:09:35 +0100144 params["params1"]["ip"] = self.getNextIp()
145 if isinstance( node2, Docker ):
peustermea8db832016-03-08 10:25:58 +0100146 if "params2" not in params:
peustermc3b977e2016-01-12 10:09:35 +0100147 params["params2"] = {}
peustermea8db832016-03-08 10:25:58 +0100148 if "ip" not in params["params2"]:
peustermc3b977e2016-01-12 10:09:35 +0100149 params["params2"]["ip"] = self.getNextIp()
peustermea8db832016-03-08 10:25:58 +0100150 # ensure that we allow TCLinks between data centers
151 # TODO this is not optimal, we use cls=Link for containers and TCLink for data centers
peusterm5877ea22016-05-11 13:44:59 +0200152 # see Containernet issue: https://github.com/mpeuster/containernet/issues/3
peustermea8db832016-03-08 10:25:58 +0100153 if "cls" not in params:
154 params["cls"] = TCLink
peustermc3b977e2016-01-12 10:09:35 +0100155
peusterm5877ea22016-05-11 13:44:59 +0200156 link = Containernet.addLink(self, node1, node2, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100157
stevenvanrossemc1149022016-04-11 01:16:44 +0200158 # try to give container interfaces a default id
159 node1_port_id = node1.ports[link.intf1]
160 if isinstance(node1, Docker):
161 if "id" in params["params1"]:
162 node1_port_id = params["params1"]["id"]
stevenvanrossem5b376412016-05-04 15:34:49 +0200163 node1_port_name = link.intf1.name
stevenvanrossemc1149022016-04-11 01:16:44 +0200164
165 node2_port_id = node2.ports[link.intf2]
166 if isinstance(node2, Docker):
167 if "id" in params["params2"]:
168 node2_port_id = params["params2"]["id"]
stevenvanrossem5b376412016-05-04 15:34:49 +0200169 node2_port_name = link.intf2.name
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200170
171
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100172 # add edge and assigned port number to graph in both directions between node1 and node2
stevenvanrossemc1149022016-04-11 01:16:44 +0200173 # port_id: id given in descriptor (if available, otherwise same as port)
peusterm5877ea22016-05-11 13:44:59 +0200174 # port: portnumber assigned by Containernet
stevenvanrossemc1149022016-04-11 01:16:44 +0200175
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200176 attr_dict = {}
177 # possible weight metrics allowed by TClink class:
178 weight_metrics = ['bw', 'delay', 'jitter', 'loss']
179 edge_attributes = [p for p in params if p in weight_metrics]
180 for attr in edge_attributes:
181 # if delay: strip ms (need number as weight in graph)
182 match = re.search('([0-9]*\.?[0-9]+)', params[attr])
183 if match:
184 attr_number = match.group(1)
185 else:
186 attr_number = None
187 attr_dict[attr] = attr_number
188
189
stevenvanrossem5b376412016-05-04 15:34:49 +0200190 attr_dict2 = {'src_port_id': node1_port_id, 'src_port_nr': node1.ports[link.intf1],
191 'src_port_name': node1_port_name,
192 'dst_port_id': node2_port_id, 'dst_port_nr': node2.ports[link.intf2],
193 'dst_port_name': node2_port_name}
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200194 attr_dict2.update(attr_dict)
195 self.DCNetwork_graph.add_edge(node1.name, node2.name, attr_dict=attr_dict2)
196
stevenvanrossem5b376412016-05-04 15:34:49 +0200197 attr_dict2 = {'src_port_id': node2_port_id, 'src_port_nr': node2.ports[link.intf2],
198 'src_port_name': node2_port_name,
199 'dst_port_id': node1_port_id, 'dst_port_nr': node1.ports[link.intf1],
200 'dst_port_name': node1_port_name}
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200201 attr_dict2.update(attr_dict)
202 self.DCNetwork_graph.add_edge(node2.name, node1.name, attr_dict=attr_dict2)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100203
204 return link
peustermcbcd4c22015-12-28 11:33:42 +0100205
peusterma47db032016-02-04 14:55:29 +0100206 def addDocker( self, label, **params ):
peusterm5b844a12016-01-11 15:58:15 +0100207 """
peusterm293cbc32016-01-13 17:05:28 +0100208 Wrapper for addDocker method to use custom container class.
peusterm5b844a12016-01-11 15:58:15 +0100209 """
stevenvanrossem8fbf9782016-02-17 11:40:23 +0100210 self.DCNetwork_graph.add_node(label)
peusterm5877ea22016-05-11 13:44:59 +0200211 return Containernet.addDocker(self, label, cls=EmulatorCompute, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100212
stevenvanrossem8fbf9782016-02-17 11:40:23 +0100213 def removeDocker( self, label, **params ):
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100214 """
215 Wrapper for removeDocker method to update graph.
216 """
stevenvanrossem8fbf9782016-02-17 11:40:23 +0100217 self.DCNetwork_graph.remove_node(label)
peusterm5877ea22016-05-11 13:44:59 +0200218 return Containernet.removeDocker(self, label, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100219
220 def addSwitch( self, name, add_to_graph=True, **params ):
221 """
222 Wrapper for addSwitch method to store switch also in graph.
223 """
224 if add_to_graph:
225 self.DCNetwork_graph.add_node(name)
peusterm5877ea22016-05-11 13:44:59 +0200226 return Containernet.addSwitch(self, name, protocols='OpenFlow10,OpenFlow12,OpenFlow13', **params)
peustermc3b977e2016-01-12 10:09:35 +0100227
peustermbd44f4a2016-01-13 14:53:30 +0100228 def getAllContainers(self):
229 """
230 Returns a list with all containers within all data centers.
231 """
232 all_containers = []
233 for dc in self.dcs.itervalues():
234 all_containers += dc.listCompute()
235 return all_containers
236
peustermcbcd4c22015-12-28 11:33:42 +0100237 def start(self):
238 # start
239 for dc in self.dcs.itervalues():
240 dc.start()
peusterm5877ea22016-05-11 13:44:59 +0200241 Containernet.start(self)
peustermcbcd4c22015-12-28 11:33:42 +0100242
243 def stop(self):
stevenvanrossem60670da2016-04-15 15:31:28 +0200244
stevenvanrossemc6abf132016-04-14 11:15:58 +0200245 # stop the monitor agent
stevenvanrossem60670da2016-04-15 15:31:28 +0200246 if self.monitor_agent is not None:
247 self.monitor_agent.stop()
peustermcbcd4c22015-12-28 11:33:42 +0100248
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200249 # stop emulator net
peusterm5877ea22016-05-11 13:44:59 +0200250 Containernet.stop(self)
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200251
252 # stop Ryu controller
253 self.stopRyu()
254
255
peustermcbcd4c22015-12-28 11:33:42 +0100256 def CLI(self):
peusterm293cbc32016-01-13 17:05:28 +0100257 CLI(self)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100258
259 # to remove chain do setChain( src, dst, cmd='del-flows')
stevenvanrossem461941c2016-05-10 11:41:29 +0200260 def setChain(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
261 cmd = kwargs.get('cmd')
262 if cmd == 'add-flow':
263 ret = self._chainAddFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface, **kwargs)
264 if kwargs.get('bidirectional'):
stevenvanrossem81955a52016-05-12 14:34:12 +0200265 ret = ret +'\n' + self._chainAddFlow(vnf_dst_name, vnf_src_name, vnf_dst_interface, vnf_src_interface, **kwargs)
stevenvanrossem9315da42016-04-11 12:10:06 +0200266
stevenvanrosseme131bf52016-07-14 11:42:09 +0200267 elif cmd == 'del-flows':
stevenvanrossem461941c2016-05-10 11:41:29 +0200268 ret = self._chainAddFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface, **kwargs)
269 if kwargs.get('bidirectional'):
stevenvanrossem81955a52016-05-12 14:34:12 +0200270 ret = ret + '\n' + self._chainAddFlow(vnf_dst_name, vnf_src_name, vnf_dst_interface, vnf_src_interface, **kwargs)
stevenvanrossem461941c2016-05-10 11:41:29 +0200271
272 else:
stevenvanrossem81955a52016-05-12 14:34:12 +0200273 ret = "Command unknown"
274
275 return ret
stevenvanrossem461941c2016-05-10 11:41:29 +0200276
277
278 def _chainAddFlow(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
279
peusterm53d3c142016-07-18 10:10:11 +0200280 src_sw = None
281 dst_sw = None
282 src_sw_inport_nr = 0
283 dst_sw_outport_nr = 0
284
285 LOG.debug("call chainAddFlow vnf_src_name=%r, vnf_src_interface=%r, vnf_dst_name=%r, vnf_dst_interface=%r",
286 vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface)
287
stevenvanrossem9315da42016-04-11 12:10:06 +0200288 #check if port is specified (vnf:port)
stevenvanrossemed711fd2016-04-11 16:59:29 +0200289 if vnf_src_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200290 # take first interface by default
291 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
292 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200293 vnf_src_interface = link_dict[0]['src_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200294
stevenvanrossem9315da42016-04-11 12:10:06 +0200295 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
296 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
297 for link in link_dict:
peusterm53d3c142016-07-18 10:10:11 +0200298 if (link_dict[link]['src_port_id'] == vnf_src_interface or
299 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 +0200300 # found the right link and connected switch
stevenvanrossem9315da42016-04-11 12:10:06 +0200301 src_sw = connected_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200302 src_sw_inport_nr = link_dict[link]['dst_port_nr']
stevenvanrossem9315da42016-04-11 12:10:06 +0200303 break
304
stevenvanrossemed711fd2016-04-11 16:59:29 +0200305 if vnf_dst_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200306 # take first interface by default
307 connected_sw = self.DCNetwork_graph.neighbors(vnf_dst_name)[0]
308 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200309 vnf_dst_interface = link_dict[0]['dst_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200310
311 vnf_dst_name = vnf_dst_name.split(':')[0]
312 for connected_sw in self.DCNetwork_graph.neighbors(vnf_dst_name):
313 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
314 for link in link_dict:
peusterm53d3c142016-07-18 10:10:11 +0200315 if link_dict[link]['dst_port_id'] == vnf_dst_interface or \
316 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 +0200317 # found the right link and connected switch
318 dst_sw = connected_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200319 dst_sw_outport_nr = link_dict[link]['src_port_nr']
stevenvanrossem9315da42016-04-11 12:10:06 +0200320 break
321
322
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100323 # get shortest path
stevenvanrossem9315da42016-04-11 12:10:06 +0200324 try:
stevenvanrossem1f68afb2016-05-02 15:36:04 +0200325 # returns the first found shortest path
326 # if all shortest paths are wanted, use: all_shortest_paths
stevenvanrossem461941c2016-05-10 11:41:29 +0200327 path = nx.shortest_path(self.DCNetwork_graph, src_sw, dst_sw, weight=kwargs.get('weight'))
stevenvanrossem9315da42016-04-11 12:10:06 +0200328 except:
peusterm53d3c142016-07-18 10:10:11 +0200329 LOG.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
330 vnf_src_name, vnf_dst_name, src_sw, dst_sw))
peustermf9a817d2016-07-18 09:06:04 +0200331 LOG.debug("Graph nodes: %r" % self.DCNetwork_graph.nodes())
332 LOG.debug("Graph edges: %r" % self.DCNetwork_graph.edges())
peusterm53d3c142016-07-18 10:10:11 +0200333 for e, v in self.DCNetwork_graph.edges():
334 LOG.debug("%r" % self.DCNetwork_graph[e][v])
stevenvanrossem9315da42016-04-11 12:10:06 +0200335 return "No path could be found between {0} and {1}".format(vnf_src_name, vnf_dst_name)
336
peustermf9a817d2016-07-18 09:06:04 +0200337 LOG.info("Path between {0} and {1}: {2}".format(vnf_src_name, vnf_dst_name, path))
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100338
stevenvanrossem9315da42016-04-11 12:10:06 +0200339 current_hop = src_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200340 switch_inport_nr = src_sw_inport_nr
stevenvanrossem9315da42016-04-11 12:10:06 +0200341
stevenvanrossem461941c2016-05-10 11:41:29 +0200342 # choose free vlan if path contains more than 1 switch
stevenvanrossem27b6d952016-05-10 16:37:57 +0200343 cmd = kwargs.get('cmd')
344 vlan = None
345 if cmd == 'add-flow':
346 if len(path) > 1:
347 vlan = self.vlans.pop()
stevenvanrossem461941c2016-05-10 11:41:29 +0200348
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100349 for i in range(0,len(path)):
stevenvanrossem9315da42016-04-11 12:10:06 +0200350 current_node = self.getNodeByName(current_hop)
stevenvanrossem461941c2016-05-10 11:41:29 +0200351
stevenvanrossem9315da42016-04-11 12:10:06 +0200352 if path.index(current_hop) < len(path)-1:
353 next_hop = path[path.index(current_hop)+1]
354 else:
355 #last switch reached
356 next_hop = vnf_dst_name
357
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100358 next_node = self.getNodeByName(next_hop)
359
360 if next_hop == vnf_dst_name:
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200361 switch_outport_nr = dst_sw_outport_nr
peustermf9a817d2016-07-18 09:06:04 +0200362 LOG.info("end node reached: {0}".format(vnf_dst_name))
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100363 elif not isinstance( next_node, OVSSwitch ):
peustermf9a817d2016-07-18 09:06:04 +0200364 LOG.info("Next node: {0} is not a switch".format(next_hop))
stevenvanrossemeefea6c2016-02-17 12:03:26 +0100365 return "Next node: {0} is not a switch".format(next_hop)
stevenvanrossem9315da42016-04-11 12:10:06 +0200366 else:
stevenvanrossemed711fd2016-04-11 16:59:29 +0200367 # take first link between switches by default
stevenvanrossem9315da42016-04-11 12:10:06 +0200368 index_edge_out = 0
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200369 switch_outport_nr = self.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
stevenvanrossem9315da42016-04-11 12:10:06 +0200370
stevenvanrossem9315da42016-04-11 12:10:06 +0200371
stevenvanrossem461941c2016-05-10 11:41:29 +0200372 # set of entry via ovs-ofctl
stevenvanrossem9315da42016-04-11 12:10:06 +0200373 if isinstance( current_node, OVSSwitch ):
stevenvanrossem461941c2016-05-10 11:41:29 +0200374 kwargs['vlan'] = vlan
375 kwargs['path'] = path
376 kwargs['current_hop'] = current_hop
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200377
378 if self.controller == RemoteController:
379 ## set flow entry via ryu rest api
380 self._set_flow_entry_ryu_rest(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
381 else:
382 ## set flow entry via ovs-ofctl
383 self._set_flow_entry_dpctl(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
384
385
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100386
stevenvanrossemed711fd2016-04-11 16:59:29 +0200387 # take first link between switches by default
388 if isinstance( next_node, OVSSwitch ):
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200389 switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
stevenvanrossemed711fd2016-04-11 16:59:29 +0200390 current_hop = next_hop
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100391
stevenvanrossem27b6d952016-05-10 16:37:57 +0200392 return "path {2} between {0} and {1}".format(vnf_src_name, vnf_dst_name, cmd)
393
394 def _set_flow_entry_ryu_rest(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
395 match = 'in_port=%s' % switch_inport_nr
396
397 cookie = kwargs.get('cookie')
398 match_input = kwargs.get('match')
399 cmd = kwargs.get('cmd')
400 path = kwargs.get('path')
401 current_hop = kwargs.get('current_hop')
402 vlan = kwargs.get('vlan')
403
404 s = ','
405 if match_input:
406 match = s.join([match, match_input])
407
408 flow = {}
409 flow['dpid'] = int(node.dpid, 16)
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200410
stevenvanrossem27b6d952016-05-10 16:37:57 +0200411 if cookie:
412 flow['cookie'] = int(cookie)
413
414
415 flow['actions'] = []
416
417 # possible Ryu actions, match fields:
418 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#add-a-flow-entry
419 if cmd == 'add-flow':
420 prefix = 'stats/flowentry/add'
stevenvanrossem27b6d952016-05-10 16:37:57 +0200421 if vlan != None:
422 if path.index(current_hop) == 0: # first node
423 action = {}
424 action['type'] = 'PUSH_VLAN' # Push a new VLAN tag if a input frame is non-VLAN-tagged
425 action['ethertype'] = 33024 # Ethertype 0x8100(=33024): IEEE 802.1Q VLAN-tagged frame
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200426 flow['actions'].append(action)
427 action = {}
stevenvanrossem27b6d952016-05-10 16:37:57 +0200428 action['type'] = 'SET_FIELD'
429 action['field'] = 'vlan_vid'
430 action['value'] = vlan
431 flow['actions'].append(action)
432 elif path.index(current_hop) == len(path) - 1: # last node
433 match += ',dl_vlan=%s' % vlan
434 action = {}
435 action['type'] = 'POP_VLAN'
436 flow['actions'].append(action)
437 else: # middle nodes
438 match += ',dl_vlan=%s' % vlan
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200439 # output action must come last
440 action = {}
441 action['type'] = 'OUTPUT'
442 action['port'] = switch_outport_nr
443 flow['actions'].append(action)
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200444
stevenvanrossem27b6d952016-05-10 16:37:57 +0200445 elif cmd == 'del-flows':
stevenvanrossem27b6d952016-05-10 16:37:57 +0200446 prefix = 'stats/flowentry/delete'
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200447
stevenvanrossem27b6d952016-05-10 16:37:57 +0200448 if cookie:
stevenvanrossem1ef77022016-05-12 16:36:10 +0200449 # TODO: add cookie_mask as argument
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200450 flow['cookie_mask'] = int('0xffffffffffffffff', 16) # need full mask to match complete cookie
stevenvanrossem27b6d952016-05-10 16:37:57 +0200451
452 action = {}
453 action['type'] = 'OUTPUT'
454 action['port'] = switch_outport_nr
455 flow['actions'].append(action)
456
457 flow['match'] = self._parse_match(match)
458 self.ryu_REST(prefix, data=flow)
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100459
stevenvanrossem461941c2016-05-10 11:41:29 +0200460 def _set_flow_entry_dpctl(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
stevenvanrossem23c48092016-05-06 17:21:12 +0200461 match = 'in_port=%s' % switch_inport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200462
463 cookie = kwargs.get('cookie')
464 match_input = kwargs.get('match')
465 cmd = kwargs.get('cmd')
466 path = kwargs.get('path')
467 current_hop = kwargs.get('current_hop')
468 vlan = kwargs.get('vlan')
469
stevenvanrossem898a2af2016-05-06 18:28:57 +0200470 s = ','
471 if cookie:
472 cookie = 'cookie=%s' % cookie
473 match = s.join([cookie, match])
stevenvanrossem23c48092016-05-06 17:21:12 +0200474 if match_input:
stevenvanrossem23c48092016-05-06 17:21:12 +0200475 match = s.join([match, match_input])
stevenvanrossem23c48092016-05-06 17:21:12 +0200476 if cmd == 'add-flow':
477 action = 'action=%s' % switch_outport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200478 if vlan != None:
479 if path.index(current_hop) == 0: # first node
480 action = ('action=mod_vlan_vid:%s' % vlan) + (',output=%s' % switch_outport_nr)
481 match = '-O OpenFlow13 ' + match
482 elif path.index(current_hop) == len(path) - 1: # last node
483 match += ',dl_vlan=%s' % vlan
484 action = 'action=strip_vlan,output=%s' % switch_outport_nr
485 else: # middle nodes
486 match += ',dl_vlan=%s' % vlan
stevenvanrossem23c48092016-05-06 17:21:12 +0200487 ofcmd = s.join([match, action])
488 elif cmd == 'del-flows':
489 ofcmd = match
490 else:
491 ofcmd = ''
492
493 node.dpctl(cmd, ofcmd)
peustermf9a817d2016-07-18 09:06:04 +0200494 LOG.info("{3} in switch: {0} in_port: {1} out_port: {2}".format(node.name, switch_inport_nr,
stevenvanrossem461941c2016-05-10 11:41:29 +0200495 switch_outport_nr, cmd))
stevenvanrossem23c48092016-05-06 17:21:12 +0200496
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100497 # start Ryu Openflow controller as Remote Controller for the DCNetwork
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200498 def startRyu(self, learning_switch=True):
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100499 # start Ryu controller with rest-API
500 python_install_path = site.getsitepackages()[0]
501 ryu_path = python_install_path + '/ryu/app/simple_switch_13.py'
peustermde14f332016-03-15 16:14:21 +0100502 ryu_path2 = python_install_path + '/ryu/app/ofctl_rest.py'
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100503 # change the default Openflow controller port to 6653 (official IANA-assigned port number), as used by Mininet
504 # Ryu still uses 6633 as default
505 ryu_option = '--ofp-tcp-listen-port'
506 ryu_of_port = '6653'
peustermde14f332016-03-15 16:14:21 +0100507 ryu_cmd = 'ryu-manager'
peustermef6629e2016-03-14 17:21:56 +0100508 FNULL = open("/tmp/ryu.log", 'w')
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200509 if learning_switch:
510 self.ryu_process = Popen([ryu_cmd, ryu_path, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
511 else:
stevenvanrossem73efd192016-06-29 01:44:07 +0200512 # no learning switch, but with rest api
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200513 self.ryu_process = Popen([ryu_cmd, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
peusterm391773a2016-03-14 17:40:43 +0100514 time.sleep(1)
515
516 def stopRyu(self):
peustermde14f332016-03-15 16:14:21 +0100517 if self.ryu_process is not None:
peusterm391773a2016-03-14 17:40:43 +0100518 self.ryu_process.terminate()
519 self.ryu_process.kill()
520
stevenvanrossem27b6d952016-05-10 16:37:57 +0200521 def ryu_REST(self, prefix, dpid=None, data=None):
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200522 try:
523 if dpid:
524 url = self.ryu_REST_api + '/' + str(prefix) + '/' + str(dpid)
525 else:
526 url = self.ryu_REST_api + '/' + str(prefix)
527 if data:
peustermf9a817d2016-07-18 09:06:04 +0200528 #LOG.info('POST: {0}'.format(str(data)))
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200529 req = urllib2.Request(url, str(data))
530 else:
531 req = urllib2.Request(url)
stevenvanrossem27b6d952016-05-10 16:37:57 +0200532
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200533 ret = urllib2.urlopen(req).read()
534 return ret
535 except:
peustermf9a817d2016-07-18 09:06:04 +0200536 LOG.info('error url: {0}'.format(str(url)))
537 if data: LOG.info('error POST: {0}'.format(str(data)))
stevenvanrossem27b6d952016-05-10 16:37:57 +0200538
539 # need to respect that some match fields must be integers
540 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#description-of-match-and-actions
541 def _parse_match(self, match):
542 matches = match.split(',')
543 dict = {}
544 for m in matches:
545 match = m.split('=')
546 if len(match) == 2:
547 try:
548 m2 = int(match[1], 0)
549 except:
550 m2 = match[1]
551
552 dict.update({match[0]:m2})
553 return dict
554