blob: bca20b2dfcf5ce97ad2a5d17718534352ab82158 [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
peusterm8b04b532016-07-19 16:55:38 +020042from mininet.clean import cleanup
stevenvanrossemc5a536a2016-02-16 14:52:39 +010043import networkx as nx
cgeoffroy9524ad32016-03-03 18:24:15 +010044from emuvim.dcemulator.monitoring import DCNetworkMonitor
cgeoffroy9524ad32016-03-03 18:24:15 +010045from emuvim.dcemulator.node import Datacenter, EmulatorCompute
peusterm42f08be2016-03-10 21:56:34 +010046from emuvim.dcemulator.resourcemodel import ResourceModelRegistrar
peustermcbcd4c22015-12-28 11:33:42 +010047
peustermf9a817d2016-07-18 09:06:04 +020048LOG = logging.getLogger("dcemulator.net")
49LOG.setLevel(logging.DEBUG)
50
peusterm5877ea22016-05-11 13:44:59 +020051class DCNetwork(Containernet):
peusterme4e89d32016-01-07 09:14:54 +010052 """
peusterm5877ea22016-05-11 13:44:59 +020053 Wraps the original Mininet/Containernet class and provides
peusterme4e89d32016-01-07 09:14:54 +010054 methods to add data centers, switches, etc.
55
56 This class is used by topology definition scripts.
57 """
peustermcbcd4c22015-12-28 11:33:42 +010058
peusterm0ec25102016-04-16 02:16:20 +020059 def __init__(self, controller=RemoteController, monitor=False,
stevenvanrossem7cd3c252016-05-11 22:55:15 +020060 enable_learning = True, # in case of RemoteController (Ryu), learning switch behavior can be turned off/on
peusterma4d84792016-03-25 12:27:07 +010061 dc_emulation_max_cpu=1.0, # fraction of overall CPU time for emulation
62 dc_emulation_max_mem=512, # emulation max mem in MB
63 **kwargs):
peusterm42f08be2016-03-10 21:56:34 +010064 """
peusterm5877ea22016-05-11 13:44:59 +020065 Create an extended version of a Containernet network
peusterm42f08be2016-03-10 21:56:34 +010066 :param dc_emulation_max_cpu: max. CPU time used by containers in data centers
67 :param kwargs: path through for Mininet parameters
68 :return:
69 """
peusterm8b04b532016-07-19 16:55:38 +020070 # members
peustermcbcd4c22015-12-28 11:33:42 +010071 self.dcs = {}
peusterm8b04b532016-07-19 16:55:38 +020072 self.ryu_process = None
peusterm42f08be2016-03-10 21:56:34 +010073
peusterm8b04b532016-07-19 16:55:38 +020074 # always cleanup environment before we start the emulator
stevenvanrossem89706802016-07-19 02:54:45 +020075 self.killRyu()
peusterm8b04b532016-07-19 16:55:38 +020076 cleanup()
stevenvanrossem89706802016-07-19 02:54:45 +020077
peusterm293cbc32016-01-13 17:05:28 +010078 # call original Docker.__init__ and setup default controller
peusterm5877ea22016-05-11 13:44:59 +020079 Containernet.__init__(
stevenvanrossem7cd3c252016-05-11 22:55:15 +020080 self, switch=OVSKernelSwitch, controller=controller, **kwargs)
stevenvanrossemc5a536a2016-02-16 14:52:39 +010081
peustermde14f332016-03-15 16:14:21 +010082 # Ryu management
peustermde14f332016-03-15 16:14:21 +010083 if controller == RemoteController:
84 # start Ryu controller
stevenvanrossem7cd3c252016-05-11 22:55:15 +020085 self.startRyu(learning_switch=enable_learning)
stevenvanrossem9ebd0942016-02-22 10:13:05 +010086
peustermde14f332016-03-15 16:14:21 +010087 # add the specified controller
peustermef6629e2016-03-14 17:21:56 +010088 self.addController('c0', controller=controller)
89
90 # graph of the complete DC network
stevenvanrossemc1149022016-04-11 01:16:44 +020091 self.DCNetwork_graph = nx.MultiDiGraph()
peustermef6629e2016-03-14 17:21:56 +010092
stevenvanrossem461941c2016-05-10 11:41:29 +020093 # initialize pool of vlan tags to setup the SDN paths
94 self.vlans = range(4096)[::-1]
95
stevenvanrossem27b6d952016-05-10 16:37:57 +020096 # link to Ryu REST_API
97 ryu_ip = '0.0.0.0'
98 ryu_port = '8080'
99 self.ryu_REST_api = 'http://{0}:{1}'.format(ryu_ip, ryu_port)
100
peustermef6629e2016-03-14 17:21:56 +0100101 # monitoring agent
stevenvanrossem60670da2016-04-15 15:31:28 +0200102 if monitor:
103 self.monitor_agent = DCNetworkMonitor(self)
104 else:
105 self.monitor_agent = None
peustermef6629e2016-03-14 17:21:56 +0100106
peusterm42f08be2016-03-10 21:56:34 +0100107 # initialize resource model registrar
peusterma4d84792016-03-25 12:27:07 +0100108 self.rm_registrar = ResourceModelRegistrar(
109 dc_emulation_max_cpu, dc_emulation_max_mem)
peustermcbcd4c22015-12-28 11:33:42 +0100110
peusterm60bf8b82016-04-06 14:12:35 +0200111 def addDatacenter(self, label, metadata={}, resource_log_path=None):
peustermcbcd4c22015-12-28 11:33:42 +0100112 """
113 Create and add a logical cloud data center to the network.
114 """
peusterma47db032016-02-04 14:55:29 +0100115 if label in self.dcs:
116 raise Exception("Data center label already exists: %s" % label)
peusterm60bf8b82016-04-06 14:12:35 +0200117 dc = Datacenter(label, metadata=metadata, resource_log_path=resource_log_path)
peustermcbcd4c22015-12-28 11:33:42 +0100118 dc.net = self # set reference to network
peusterma47db032016-02-04 14:55:29 +0100119 self.dcs[label] = dc
peustermcbcd4c22015-12-28 11:33:42 +0100120 dc.create() # finally create the data center in our Mininet instance
peustermf9a817d2016-07-18 09:06:04 +0200121 LOG.info("added data center: %s" % label)
peustermcbcd4c22015-12-28 11:33:42 +0100122 return dc
123
peusterme6092692016-01-11 16:32:58 +0100124 def addLink(self, node1, node2, **params):
peusterm5b844a12016-01-11 15:58:15 +0100125 """
126 Able to handle Datacenter objects as link
127 end points.
128 """
peustermcbcd4c22015-12-28 11:33:42 +0100129 assert node1 is not None
130 assert node2 is not None
peustermf9a817d2016-07-18 09:06:04 +0200131 LOG.debug("addLink: n1=%s n2=%s" % (str(node1), str(node2)))
peustermcbcd4c22015-12-28 11:33:42 +0100132 # ensure type of node1
133 if isinstance( node1, basestring ):
134 if node1 in self.dcs:
135 node1 = self.dcs[node1].switch
peustermcbcd4c22015-12-28 11:33:42 +0100136 if isinstance( node1, Datacenter ):
137 node1 = node1.switch
138 # ensure type of node2
139 if isinstance( node2, basestring ):
140 if node2 in self.dcs:
141 node2 = self.dcs[node2].switch
peustermcbcd4c22015-12-28 11:33:42 +0100142 if isinstance( node2, Datacenter ):
143 node2 = node2.switch
peustermc3b977e2016-01-12 10:09:35 +0100144 # try to give containers a default IP
145 if isinstance( node1, Docker ):
peustermea8db832016-03-08 10:25:58 +0100146 if "params1" not in params:
peustermc3b977e2016-01-12 10:09:35 +0100147 params["params1"] = {}
peustermea8db832016-03-08 10:25:58 +0100148 if "ip" not in params["params1"]:
peustermc3b977e2016-01-12 10:09:35 +0100149 params["params1"]["ip"] = self.getNextIp()
150 if isinstance( node2, Docker ):
peustermea8db832016-03-08 10:25:58 +0100151 if "params2" not in params:
peustermc3b977e2016-01-12 10:09:35 +0100152 params["params2"] = {}
peustermea8db832016-03-08 10:25:58 +0100153 if "ip" not in params["params2"]:
peustermc3b977e2016-01-12 10:09:35 +0100154 params["params2"]["ip"] = self.getNextIp()
peustermea8db832016-03-08 10:25:58 +0100155 # ensure that we allow TCLinks between data centers
156 # TODO this is not optimal, we use cls=Link for containers and TCLink for data centers
peusterm5877ea22016-05-11 13:44:59 +0200157 # see Containernet issue: https://github.com/mpeuster/containernet/issues/3
peustermea8db832016-03-08 10:25:58 +0100158 if "cls" not in params:
159 params["cls"] = TCLink
peustermc3b977e2016-01-12 10:09:35 +0100160
peusterm5877ea22016-05-11 13:44:59 +0200161 link = Containernet.addLink(self, node1, node2, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100162
stevenvanrossemc1149022016-04-11 01:16:44 +0200163 # try to give container interfaces a default id
164 node1_port_id = node1.ports[link.intf1]
165 if isinstance(node1, Docker):
166 if "id" in params["params1"]:
167 node1_port_id = params["params1"]["id"]
stevenvanrossem5b376412016-05-04 15:34:49 +0200168 node1_port_name = link.intf1.name
stevenvanrossemc1149022016-04-11 01:16:44 +0200169
170 node2_port_id = node2.ports[link.intf2]
171 if isinstance(node2, Docker):
172 if "id" in params["params2"]:
173 node2_port_id = params["params2"]["id"]
stevenvanrossem5b376412016-05-04 15:34:49 +0200174 node2_port_name = link.intf2.name
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200175
176
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100177 # add edge and assigned port number to graph in both directions between node1 and node2
stevenvanrossemc1149022016-04-11 01:16:44 +0200178 # port_id: id given in descriptor (if available, otherwise same as port)
peusterm5877ea22016-05-11 13:44:59 +0200179 # port: portnumber assigned by Containernet
stevenvanrossemc1149022016-04-11 01:16:44 +0200180
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200181 attr_dict = {}
182 # possible weight metrics allowed by TClink class:
183 weight_metrics = ['bw', 'delay', 'jitter', 'loss']
184 edge_attributes = [p for p in params if p in weight_metrics]
185 for attr in edge_attributes:
186 # if delay: strip ms (need number as weight in graph)
187 match = re.search('([0-9]*\.?[0-9]+)', params[attr])
188 if match:
189 attr_number = match.group(1)
190 else:
191 attr_number = None
192 attr_dict[attr] = attr_number
193
194
stevenvanrossem5b376412016-05-04 15:34:49 +0200195 attr_dict2 = {'src_port_id': node1_port_id, 'src_port_nr': node1.ports[link.intf1],
196 'src_port_name': node1_port_name,
197 'dst_port_id': node2_port_id, 'dst_port_nr': node2.ports[link.intf2],
198 'dst_port_name': node2_port_name}
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200199 attr_dict2.update(attr_dict)
200 self.DCNetwork_graph.add_edge(node1.name, node2.name, attr_dict=attr_dict2)
201
stevenvanrossem5b376412016-05-04 15:34:49 +0200202 attr_dict2 = {'src_port_id': node2_port_id, 'src_port_nr': node2.ports[link.intf2],
203 'src_port_name': node2_port_name,
204 'dst_port_id': node1_port_id, 'dst_port_nr': node1.ports[link.intf1],
205 'dst_port_name': node1_port_name}
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200206 attr_dict2.update(attr_dict)
207 self.DCNetwork_graph.add_edge(node2.name, node1.name, attr_dict=attr_dict2)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100208
209 return link
peustermcbcd4c22015-12-28 11:33:42 +0100210
peusterma47db032016-02-04 14:55:29 +0100211 def addDocker( self, label, **params ):
peusterm5b844a12016-01-11 15:58:15 +0100212 """
peusterm293cbc32016-01-13 17:05:28 +0100213 Wrapper for addDocker method to use custom container class.
peusterm5b844a12016-01-11 15:58:15 +0100214 """
stevenvanrossem8fbf9782016-02-17 11:40:23 +0100215 self.DCNetwork_graph.add_node(label)
peusterm5877ea22016-05-11 13:44:59 +0200216 return Containernet.addDocker(self, label, cls=EmulatorCompute, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100217
stevenvanrossem8fbf9782016-02-17 11:40:23 +0100218 def removeDocker( self, label, **params ):
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100219 """
220 Wrapper for removeDocker method to update graph.
221 """
stevenvanrossem8fbf9782016-02-17 11:40:23 +0100222 self.DCNetwork_graph.remove_node(label)
peusterm5877ea22016-05-11 13:44:59 +0200223 return Containernet.removeDocker(self, label, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100224
225 def addSwitch( self, name, add_to_graph=True, **params ):
226 """
227 Wrapper for addSwitch method to store switch also in graph.
228 """
229 if add_to_graph:
230 self.DCNetwork_graph.add_node(name)
peusterm5877ea22016-05-11 13:44:59 +0200231 return Containernet.addSwitch(self, name, protocols='OpenFlow10,OpenFlow12,OpenFlow13', **params)
peustermc3b977e2016-01-12 10:09:35 +0100232
peustermbd44f4a2016-01-13 14:53:30 +0100233 def getAllContainers(self):
234 """
235 Returns a list with all containers within all data centers.
236 """
237 all_containers = []
238 for dc in self.dcs.itervalues():
239 all_containers += dc.listCompute()
240 return all_containers
241
peustermcbcd4c22015-12-28 11:33:42 +0100242 def start(self):
243 # start
244 for dc in self.dcs.itervalues():
245 dc.start()
peusterm5877ea22016-05-11 13:44:59 +0200246 Containernet.start(self)
peustermcbcd4c22015-12-28 11:33:42 +0100247
248 def stop(self):
stevenvanrossem60670da2016-04-15 15:31:28 +0200249
stevenvanrossemc6abf132016-04-14 11:15:58 +0200250 # stop the monitor agent
stevenvanrossem60670da2016-04-15 15:31:28 +0200251 if self.monitor_agent is not None:
252 self.monitor_agent.stop()
peustermcbcd4c22015-12-28 11:33:42 +0100253
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200254 # stop emulator net
peusterm5877ea22016-05-11 13:44:59 +0200255 Containernet.stop(self)
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200256
257 # stop Ryu controller
peusterm8b04b532016-07-19 16:55:38 +0200258 self.killRyu()
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200259
260
peustermcbcd4c22015-12-28 11:33:42 +0100261 def CLI(self):
peusterm293cbc32016-01-13 17:05:28 +0100262 CLI(self)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100263
264 # to remove chain do setChain( src, dst, cmd='del-flows')
stevenvanrossem461941c2016-05-10 11:41:29 +0200265 def setChain(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
266 cmd = kwargs.get('cmd')
267 if cmd == 'add-flow':
268 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)
stevenvanrossem9315da42016-04-11 12:10:06 +0200271
stevenvanrosseme131bf52016-07-14 11:42:09 +0200272 elif cmd == 'del-flows':
stevenvanrossem461941c2016-05-10 11:41:29 +0200273 ret = self._chainAddFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface, **kwargs)
274 if kwargs.get('bidirectional'):
stevenvanrossem81955a52016-05-12 14:34:12 +0200275 ret = ret + '\n' + self._chainAddFlow(vnf_dst_name, vnf_src_name, vnf_dst_interface, vnf_src_interface, **kwargs)
stevenvanrossem461941c2016-05-10 11:41:29 +0200276
277 else:
stevenvanrossem81955a52016-05-12 14:34:12 +0200278 ret = "Command unknown"
279
280 return ret
stevenvanrossem461941c2016-05-10 11:41:29 +0200281
282
283 def _chainAddFlow(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
284
peusterm53d3c142016-07-18 10:10:11 +0200285 src_sw = None
286 dst_sw = None
287 src_sw_inport_nr = 0
288 dst_sw_outport_nr = 0
289
290 LOG.debug("call chainAddFlow vnf_src_name=%r, vnf_src_interface=%r, vnf_dst_name=%r, vnf_dst_interface=%r",
291 vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface)
292
stevenvanrossem9315da42016-04-11 12:10:06 +0200293 #check if port is specified (vnf:port)
stevenvanrossemed711fd2016-04-11 16:59:29 +0200294 if vnf_src_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200295 # take first interface by default
296 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
297 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200298 vnf_src_interface = link_dict[0]['src_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200299
stevenvanrossem9315da42016-04-11 12:10:06 +0200300 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
301 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
302 for link in link_dict:
peusterm53d3c142016-07-18 10:10:11 +0200303 if (link_dict[link]['src_port_id'] == vnf_src_interface or
304 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 +0200305 # found the right link and connected switch
stevenvanrossem9315da42016-04-11 12:10:06 +0200306 src_sw = connected_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200307 src_sw_inport_nr = link_dict[link]['dst_port_nr']
stevenvanrossem9315da42016-04-11 12:10:06 +0200308 break
309
stevenvanrossemed711fd2016-04-11 16:59:29 +0200310 if vnf_dst_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200311 # take first interface by default
312 connected_sw = self.DCNetwork_graph.neighbors(vnf_dst_name)[0]
313 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200314 vnf_dst_interface = link_dict[0]['dst_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200315
316 vnf_dst_name = vnf_dst_name.split(':')[0]
317 for connected_sw in self.DCNetwork_graph.neighbors(vnf_dst_name):
318 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
319 for link in link_dict:
peusterm53d3c142016-07-18 10:10:11 +0200320 if link_dict[link]['dst_port_id'] == vnf_dst_interface or \
321 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 +0200322 # found the right link and connected switch
323 dst_sw = connected_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200324 dst_sw_outport_nr = link_dict[link]['src_port_nr']
stevenvanrossem9315da42016-04-11 12:10:06 +0200325 break
326
327
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100328 # get shortest path
stevenvanrossem9315da42016-04-11 12:10:06 +0200329 try:
stevenvanrossem1f68afb2016-05-02 15:36:04 +0200330 # returns the first found shortest path
331 # if all shortest paths are wanted, use: all_shortest_paths
stevenvanrossem461941c2016-05-10 11:41:29 +0200332 path = nx.shortest_path(self.DCNetwork_graph, src_sw, dst_sw, weight=kwargs.get('weight'))
stevenvanrossem9315da42016-04-11 12:10:06 +0200333 except:
peusterm53d3c142016-07-18 10:10:11 +0200334 LOG.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
335 vnf_src_name, vnf_dst_name, src_sw, dst_sw))
peustermf9a817d2016-07-18 09:06:04 +0200336 LOG.debug("Graph nodes: %r" % self.DCNetwork_graph.nodes())
337 LOG.debug("Graph edges: %r" % self.DCNetwork_graph.edges())
peusterm53d3c142016-07-18 10:10:11 +0200338 for e, v in self.DCNetwork_graph.edges():
339 LOG.debug("%r" % self.DCNetwork_graph[e][v])
stevenvanrossem9315da42016-04-11 12:10:06 +0200340 return "No path could be found between {0} and {1}".format(vnf_src_name, vnf_dst_name)
341
peustermf9a817d2016-07-18 09:06:04 +0200342 LOG.info("Path between {0} and {1}: {2}".format(vnf_src_name, vnf_dst_name, path))
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100343
stevenvanrossem9315da42016-04-11 12:10:06 +0200344 current_hop = src_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200345 switch_inport_nr = src_sw_inport_nr
stevenvanrossem9315da42016-04-11 12:10:06 +0200346
stevenvanrossem461941c2016-05-10 11:41:29 +0200347 # choose free vlan if path contains more than 1 switch
stevenvanrossem27b6d952016-05-10 16:37:57 +0200348 cmd = kwargs.get('cmd')
349 vlan = None
350 if cmd == 'add-flow':
351 if len(path) > 1:
352 vlan = self.vlans.pop()
stevenvanrossem461941c2016-05-10 11:41:29 +0200353
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100354 for i in range(0,len(path)):
stevenvanrossem9315da42016-04-11 12:10:06 +0200355 current_node = self.getNodeByName(current_hop)
stevenvanrossem461941c2016-05-10 11:41:29 +0200356
stevenvanrossem9315da42016-04-11 12:10:06 +0200357 if path.index(current_hop) < len(path)-1:
358 next_hop = path[path.index(current_hop)+1]
359 else:
360 #last switch reached
361 next_hop = vnf_dst_name
362
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100363 next_node = self.getNodeByName(next_hop)
364
365 if next_hop == vnf_dst_name:
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200366 switch_outport_nr = dst_sw_outport_nr
peustermf9a817d2016-07-18 09:06:04 +0200367 LOG.info("end node reached: {0}".format(vnf_dst_name))
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100368 elif not isinstance( next_node, OVSSwitch ):
peustermf9a817d2016-07-18 09:06:04 +0200369 LOG.info("Next node: {0} is not a switch".format(next_hop))
stevenvanrossemeefea6c2016-02-17 12:03:26 +0100370 return "Next node: {0} is not a switch".format(next_hop)
stevenvanrossem9315da42016-04-11 12:10:06 +0200371 else:
stevenvanrossemed711fd2016-04-11 16:59:29 +0200372 # take first link between switches by default
stevenvanrossem9315da42016-04-11 12:10:06 +0200373 index_edge_out = 0
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200374 switch_outport_nr = self.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
stevenvanrossem9315da42016-04-11 12:10:06 +0200375
stevenvanrossem9315da42016-04-11 12:10:06 +0200376
stevenvanrossem461941c2016-05-10 11:41:29 +0200377 # set of entry via ovs-ofctl
stevenvanrossem9315da42016-04-11 12:10:06 +0200378 if isinstance( current_node, OVSSwitch ):
stevenvanrossem461941c2016-05-10 11:41:29 +0200379 kwargs['vlan'] = vlan
380 kwargs['path'] = path
381 kwargs['current_hop'] = current_hop
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200382
383 if self.controller == RemoteController:
384 ## set flow entry via ryu rest api
385 self._set_flow_entry_ryu_rest(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
386 else:
387 ## set flow entry via ovs-ofctl
388 self._set_flow_entry_dpctl(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
389
390
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100391
stevenvanrossemed711fd2016-04-11 16:59:29 +0200392 # take first link between switches by default
393 if isinstance( next_node, OVSSwitch ):
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200394 switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
stevenvanrossemed711fd2016-04-11 16:59:29 +0200395 current_hop = next_hop
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100396
stevenvanrossem27b6d952016-05-10 16:37:57 +0200397 return "path {2} between {0} and {1}".format(vnf_src_name, vnf_dst_name, cmd)
398
399 def _set_flow_entry_ryu_rest(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
400 match = 'in_port=%s' % switch_inport_nr
401
402 cookie = kwargs.get('cookie')
403 match_input = kwargs.get('match')
404 cmd = kwargs.get('cmd')
405 path = kwargs.get('path')
406 current_hop = kwargs.get('current_hop')
407 vlan = kwargs.get('vlan')
stevenvanrossem61699eb2016-08-05 15:57:59 +0200408 priority = kwargs.get('priority')
stevenvanrossem27b6d952016-05-10 16:37:57 +0200409
410 s = ','
411 if match_input:
412 match = s.join([match, match_input])
413
414 flow = {}
415 flow['dpid'] = int(node.dpid, 16)
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200416
stevenvanrossem27b6d952016-05-10 16:37:57 +0200417 if cookie:
418 flow['cookie'] = int(cookie)
stevenvanrossem61699eb2016-08-05 15:57:59 +0200419 if priority:
420 flow['priority'] = int(priority)
stevenvanrossem27b6d952016-05-10 16:37:57 +0200421
422 flow['actions'] = []
423
424 # possible Ryu actions, match fields:
425 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#add-a-flow-entry
426 if cmd == 'add-flow':
427 prefix = 'stats/flowentry/add'
stevenvanrossem27b6d952016-05-10 16:37:57 +0200428 if vlan != None:
429 if path.index(current_hop) == 0: # first node
430 action = {}
431 action['type'] = 'PUSH_VLAN' # Push a new VLAN tag if a input frame is non-VLAN-tagged
432 action['ethertype'] = 33024 # Ethertype 0x8100(=33024): IEEE 802.1Q VLAN-tagged frame
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200433 flow['actions'].append(action)
434 action = {}
stevenvanrossem27b6d952016-05-10 16:37:57 +0200435 action['type'] = 'SET_FIELD'
436 action['field'] = 'vlan_vid'
437 action['value'] = vlan
438 flow['actions'].append(action)
439 elif path.index(current_hop) == len(path) - 1: # last node
440 match += ',dl_vlan=%s' % vlan
441 action = {}
442 action['type'] = 'POP_VLAN'
443 flow['actions'].append(action)
444 else: # middle nodes
445 match += ',dl_vlan=%s' % vlan
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200446 # output action must come last
447 action = {}
448 action['type'] = 'OUTPUT'
449 action['port'] = switch_outport_nr
450 flow['actions'].append(action)
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200451
stevenvanrossem27b6d952016-05-10 16:37:57 +0200452 elif cmd == 'del-flows':
stevenvanrossem27b6d952016-05-10 16:37:57 +0200453 prefix = 'stats/flowentry/delete'
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200454
stevenvanrossem27b6d952016-05-10 16:37:57 +0200455 if cookie:
stevenvanrossem1ef77022016-05-12 16:36:10 +0200456 # TODO: add cookie_mask as argument
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200457 flow['cookie_mask'] = int('0xffffffffffffffff', 16) # need full mask to match complete cookie
stevenvanrossem27b6d952016-05-10 16:37:57 +0200458
459 action = {}
460 action['type'] = 'OUTPUT'
461 action['port'] = switch_outport_nr
462 flow['actions'].append(action)
463
464 flow['match'] = self._parse_match(match)
465 self.ryu_REST(prefix, data=flow)
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100466
stevenvanrossem461941c2016-05-10 11:41:29 +0200467 def _set_flow_entry_dpctl(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
stevenvanrossem23c48092016-05-06 17:21:12 +0200468 match = 'in_port=%s' % switch_inport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200469
470 cookie = kwargs.get('cookie')
471 match_input = kwargs.get('match')
472 cmd = kwargs.get('cmd')
473 path = kwargs.get('path')
474 current_hop = kwargs.get('current_hop')
475 vlan = kwargs.get('vlan')
476
stevenvanrossem898a2af2016-05-06 18:28:57 +0200477 s = ','
478 if cookie:
479 cookie = 'cookie=%s' % cookie
480 match = s.join([cookie, match])
stevenvanrossem23c48092016-05-06 17:21:12 +0200481 if match_input:
stevenvanrossem23c48092016-05-06 17:21:12 +0200482 match = s.join([match, match_input])
stevenvanrossem23c48092016-05-06 17:21:12 +0200483 if cmd == 'add-flow':
484 action = 'action=%s' % switch_outport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200485 if vlan != None:
486 if path.index(current_hop) == 0: # first node
487 action = ('action=mod_vlan_vid:%s' % vlan) + (',output=%s' % switch_outport_nr)
488 match = '-O OpenFlow13 ' + match
489 elif path.index(current_hop) == len(path) - 1: # last node
490 match += ',dl_vlan=%s' % vlan
491 action = 'action=strip_vlan,output=%s' % switch_outport_nr
492 else: # middle nodes
493 match += ',dl_vlan=%s' % vlan
stevenvanrossem23c48092016-05-06 17:21:12 +0200494 ofcmd = s.join([match, action])
495 elif cmd == 'del-flows':
496 ofcmd = match
497 else:
498 ofcmd = ''
499
500 node.dpctl(cmd, ofcmd)
peustermf9a817d2016-07-18 09:06:04 +0200501 LOG.info("{3} in switch: {0} in_port: {1} out_port: {2}".format(node.name, switch_inport_nr,
stevenvanrossem461941c2016-05-10 11:41:29 +0200502 switch_outport_nr, cmd))
stevenvanrossem23c48092016-05-06 17:21:12 +0200503
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100504 # start Ryu Openflow controller as Remote Controller for the DCNetwork
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200505 def startRyu(self, learning_switch=True):
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100506 # start Ryu controller with rest-API
507 python_install_path = site.getsitepackages()[0]
508 ryu_path = python_install_path + '/ryu/app/simple_switch_13.py'
peustermde14f332016-03-15 16:14:21 +0100509 ryu_path2 = python_install_path + '/ryu/app/ofctl_rest.py'
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100510 # change the default Openflow controller port to 6653 (official IANA-assigned port number), as used by Mininet
511 # Ryu still uses 6633 as default
512 ryu_option = '--ofp-tcp-listen-port'
513 ryu_of_port = '6653'
peustermde14f332016-03-15 16:14:21 +0100514 ryu_cmd = 'ryu-manager'
peustermef6629e2016-03-14 17:21:56 +0100515 FNULL = open("/tmp/ryu.log", 'w')
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200516 if learning_switch:
517 self.ryu_process = Popen([ryu_cmd, ryu_path, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
518 else:
stevenvanrossem73efd192016-06-29 01:44:07 +0200519 # no learning switch, but with rest api
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200520 self.ryu_process = Popen([ryu_cmd, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
peusterm391773a2016-03-14 17:40:43 +0100521 time.sleep(1)
522
peusterm8b04b532016-07-19 16:55:38 +0200523 def killRyu(self):
524 """
525 Stop the Ryu controller that might be started by son-emu.
526 :return:
527 """
528 # try it nicely
peustermde14f332016-03-15 16:14:21 +0100529 if self.ryu_process is not None:
peusterm391773a2016-03-14 17:40:43 +0100530 self.ryu_process.terminate()
531 self.ryu_process.kill()
peusterm8b04b532016-07-19 16:55:38 +0200532 # ensure its death ;-)
stevenvanrossem89706802016-07-19 02:54:45 +0200533 Popen(['pkill', '-f', 'ryu-manager'])
peusterm391773a2016-03-14 17:40:43 +0100534
stevenvanrossem27b6d952016-05-10 16:37:57 +0200535 def ryu_REST(self, prefix, dpid=None, data=None):
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200536 try:
537 if dpid:
538 url = self.ryu_REST_api + '/' + str(prefix) + '/' + str(dpid)
539 else:
540 url = self.ryu_REST_api + '/' + str(prefix)
541 if data:
peustermf9a817d2016-07-18 09:06:04 +0200542 #LOG.info('POST: {0}'.format(str(data)))
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200543 req = urllib2.Request(url, str(data))
544 else:
545 req = urllib2.Request(url)
stevenvanrossem27b6d952016-05-10 16:37:57 +0200546
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200547 ret = urllib2.urlopen(req).read()
548 return ret
549 except:
peustermf9a817d2016-07-18 09:06:04 +0200550 LOG.info('error url: {0}'.format(str(url)))
551 if data: LOG.info('error POST: {0}'.format(str(data)))
stevenvanrossem27b6d952016-05-10 16:37:57 +0200552
553 # need to respect that some match fields must be integers
554 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#description-of-match-and-actions
555 def _parse_match(self, match):
556 matches = match.split(',')
557 dict = {}
558 for m in matches:
559 match = m.split('=')
560 if len(match) == 2:
561 try:
562 m2 = int(match[1], 0)
563 except:
564 m2 = match[1]
565
566 dict.update({match[0]:m2})
567 return dict
568