blob: cbe668f1751a80213c7679d69e1e68d81c9ab2b5 [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')
408
409 s = ','
410 if match_input:
411 match = s.join([match, match_input])
412
413 flow = {}
414 flow['dpid'] = int(node.dpid, 16)
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200415
stevenvanrossem27b6d952016-05-10 16:37:57 +0200416 if cookie:
417 flow['cookie'] = int(cookie)
418
419
420 flow['actions'] = []
421
422 # possible Ryu actions, match fields:
423 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#add-a-flow-entry
424 if cmd == 'add-flow':
425 prefix = 'stats/flowentry/add'
stevenvanrossem27b6d952016-05-10 16:37:57 +0200426 if vlan != None:
427 if path.index(current_hop) == 0: # first node
428 action = {}
429 action['type'] = 'PUSH_VLAN' # Push a new VLAN tag if a input frame is non-VLAN-tagged
430 action['ethertype'] = 33024 # Ethertype 0x8100(=33024): IEEE 802.1Q VLAN-tagged frame
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200431 flow['actions'].append(action)
432 action = {}
stevenvanrossem27b6d952016-05-10 16:37:57 +0200433 action['type'] = 'SET_FIELD'
434 action['field'] = 'vlan_vid'
435 action['value'] = vlan
436 flow['actions'].append(action)
437 elif path.index(current_hop) == len(path) - 1: # last node
438 match += ',dl_vlan=%s' % vlan
439 action = {}
440 action['type'] = 'POP_VLAN'
441 flow['actions'].append(action)
442 else: # middle nodes
443 match += ',dl_vlan=%s' % vlan
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200444 # output action must come last
445 action = {}
446 action['type'] = 'OUTPUT'
447 action['port'] = switch_outport_nr
448 flow['actions'].append(action)
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200449
stevenvanrossem27b6d952016-05-10 16:37:57 +0200450 elif cmd == 'del-flows':
stevenvanrossem27b6d952016-05-10 16:37:57 +0200451 prefix = 'stats/flowentry/delete'
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200452
stevenvanrossem27b6d952016-05-10 16:37:57 +0200453 if cookie:
stevenvanrossem1ef77022016-05-12 16:36:10 +0200454 # TODO: add cookie_mask as argument
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200455 flow['cookie_mask'] = int('0xffffffffffffffff', 16) # need full mask to match complete cookie
stevenvanrossem27b6d952016-05-10 16:37:57 +0200456
457 action = {}
458 action['type'] = 'OUTPUT'
459 action['port'] = switch_outport_nr
460 flow['actions'].append(action)
461
462 flow['match'] = self._parse_match(match)
463 self.ryu_REST(prefix, data=flow)
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100464
stevenvanrossem461941c2016-05-10 11:41:29 +0200465 def _set_flow_entry_dpctl(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
stevenvanrossem23c48092016-05-06 17:21:12 +0200466 match = 'in_port=%s' % switch_inport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200467
468 cookie = kwargs.get('cookie')
469 match_input = kwargs.get('match')
470 cmd = kwargs.get('cmd')
471 path = kwargs.get('path')
472 current_hop = kwargs.get('current_hop')
473 vlan = kwargs.get('vlan')
474
stevenvanrossem898a2af2016-05-06 18:28:57 +0200475 s = ','
476 if cookie:
477 cookie = 'cookie=%s' % cookie
478 match = s.join([cookie, match])
stevenvanrossem23c48092016-05-06 17:21:12 +0200479 if match_input:
stevenvanrossem23c48092016-05-06 17:21:12 +0200480 match = s.join([match, match_input])
stevenvanrossem23c48092016-05-06 17:21:12 +0200481 if cmd == 'add-flow':
482 action = 'action=%s' % switch_outport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200483 if vlan != None:
484 if path.index(current_hop) == 0: # first node
485 action = ('action=mod_vlan_vid:%s' % vlan) + (',output=%s' % switch_outport_nr)
486 match = '-O OpenFlow13 ' + match
487 elif path.index(current_hop) == len(path) - 1: # last node
488 match += ',dl_vlan=%s' % vlan
489 action = 'action=strip_vlan,output=%s' % switch_outport_nr
490 else: # middle nodes
491 match += ',dl_vlan=%s' % vlan
stevenvanrossem23c48092016-05-06 17:21:12 +0200492 ofcmd = s.join([match, action])
493 elif cmd == 'del-flows':
494 ofcmd = match
495 else:
496 ofcmd = ''
497
498 node.dpctl(cmd, ofcmd)
peustermf9a817d2016-07-18 09:06:04 +0200499 LOG.info("{3} in switch: {0} in_port: {1} out_port: {2}".format(node.name, switch_inport_nr,
stevenvanrossem461941c2016-05-10 11:41:29 +0200500 switch_outport_nr, cmd))
stevenvanrossem23c48092016-05-06 17:21:12 +0200501
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100502 # start Ryu Openflow controller as Remote Controller for the DCNetwork
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200503 def startRyu(self, learning_switch=True):
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100504 # start Ryu controller with rest-API
505 python_install_path = site.getsitepackages()[0]
506 ryu_path = python_install_path + '/ryu/app/simple_switch_13.py'
peustermde14f332016-03-15 16:14:21 +0100507 ryu_path2 = python_install_path + '/ryu/app/ofctl_rest.py'
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100508 # change the default Openflow controller port to 6653 (official IANA-assigned port number), as used by Mininet
509 # Ryu still uses 6633 as default
510 ryu_option = '--ofp-tcp-listen-port'
511 ryu_of_port = '6653'
peustermde14f332016-03-15 16:14:21 +0100512 ryu_cmd = 'ryu-manager'
peustermef6629e2016-03-14 17:21:56 +0100513 FNULL = open("/tmp/ryu.log", 'w')
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200514 if learning_switch:
515 self.ryu_process = Popen([ryu_cmd, ryu_path, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
516 else:
stevenvanrossem73efd192016-06-29 01:44:07 +0200517 # no learning switch, but with rest api
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200518 self.ryu_process = Popen([ryu_cmd, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
peusterm391773a2016-03-14 17:40:43 +0100519 time.sleep(1)
520
peusterm8b04b532016-07-19 16:55:38 +0200521 def killRyu(self):
522 """
523 Stop the Ryu controller that might be started by son-emu.
524 :return:
525 """
526 # try it nicely
peustermde14f332016-03-15 16:14:21 +0100527 if self.ryu_process is not None:
peusterm391773a2016-03-14 17:40:43 +0100528 self.ryu_process.terminate()
529 self.ryu_process.kill()
peusterm8b04b532016-07-19 16:55:38 +0200530 # ensure its death ;-)
stevenvanrossem89706802016-07-19 02:54:45 +0200531 Popen(['pkill', '-f', 'ryu-manager'])
peusterm391773a2016-03-14 17:40:43 +0100532
stevenvanrossem27b6d952016-05-10 16:37:57 +0200533 def ryu_REST(self, prefix, dpid=None, data=None):
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200534 try:
535 if dpid:
536 url = self.ryu_REST_api + '/' + str(prefix) + '/' + str(dpid)
537 else:
538 url = self.ryu_REST_api + '/' + str(prefix)
539 if data:
peustermf9a817d2016-07-18 09:06:04 +0200540 #LOG.info('POST: {0}'.format(str(data)))
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200541 req = urllib2.Request(url, str(data))
542 else:
543 req = urllib2.Request(url)
stevenvanrossem27b6d952016-05-10 16:37:57 +0200544
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200545 ret = urllib2.urlopen(req).read()
546 return ret
547 except:
peustermf9a817d2016-07-18 09:06:04 +0200548 LOG.info('error url: {0}'.format(str(url)))
549 if data: LOG.info('error POST: {0}'.format(str(data)))
stevenvanrossem27b6d952016-05-10 16:37:57 +0200550
551 # need to respect that some match fields must be integers
552 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#description-of-match-and-actions
553 def _parse_match(self, match):
554 matches = match.split(',')
555 dict = {}
556 for m in matches:
557 match = m.split('=')
558 if len(match) == 2:
559 try:
560 m2 = int(match[1], 0)
561 except:
562 m2 = match[1]
563
564 dict.update({match[0]:m2})
565 return dict
566