blob: 51f53a3bda00765245ca16d771eccd107c58d4ef [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
stevenvanrossem9315da42016-04-11 12:10:06 +0200280 #check if port is specified (vnf:port)
stevenvanrossemed711fd2016-04-11 16:59:29 +0200281 if vnf_src_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200282 # take first interface by default
283 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
284 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200285 vnf_src_interface = link_dict[0]['src_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200286
stevenvanrossem9315da42016-04-11 12:10:06 +0200287 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
288 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
289 for link in link_dict:
stevenvanrossemed711fd2016-04-11 16:59:29 +0200290 if link_dict[link]['src_port_id'] == vnf_src_interface:
stevenvanrossem9315da42016-04-11 12:10:06 +0200291 # found the right link and connected switch
stevenvanrossem9315da42016-04-11 12:10:06 +0200292 src_sw = connected_sw
293
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200294 src_sw_inport_nr = link_dict[link]['dst_port_nr']
stevenvanrossem9315da42016-04-11 12:10:06 +0200295 break
296
stevenvanrossemed711fd2016-04-11 16:59:29 +0200297 if vnf_dst_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200298 # take first interface by default
299 connected_sw = self.DCNetwork_graph.neighbors(vnf_dst_name)[0]
300 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200301 vnf_dst_interface = link_dict[0]['dst_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200302
303 vnf_dst_name = vnf_dst_name.split(':')[0]
304 for connected_sw in self.DCNetwork_graph.neighbors(vnf_dst_name):
305 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
306 for link in link_dict:
stevenvanrossemed711fd2016-04-11 16:59:29 +0200307 if link_dict[link]['dst_port_id'] == vnf_dst_interface:
stevenvanrossem9315da42016-04-11 12:10:06 +0200308 # found the right link and connected switch
309 dst_sw = connected_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200310 dst_sw_outport_nr = link_dict[link]['src_port_nr']
stevenvanrossem9315da42016-04-11 12:10:06 +0200311 break
312
313
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100314 # get shortest path
stevenvanrossem9315da42016-04-11 12:10:06 +0200315 try:
stevenvanrossem1f68afb2016-05-02 15:36:04 +0200316 # returns the first found shortest path
317 # if all shortest paths are wanted, use: all_shortest_paths
stevenvanrossem461941c2016-05-10 11:41:29 +0200318 path = nx.shortest_path(self.DCNetwork_graph, src_sw, dst_sw, weight=kwargs.get('weight'))
stevenvanrossem9315da42016-04-11 12:10:06 +0200319 except:
peustermf9a817d2016-07-18 09:06:04 +0200320 LOG.exception("No path could be found between {0} and {1}".format(vnf_src_name, vnf_dst_name))
321 LOG.debug("Graph nodes: %r" % self.DCNetwork_graph.nodes())
322 LOG.debug("Graph edges: %r" % self.DCNetwork_graph.edges())
stevenvanrossem9315da42016-04-11 12:10:06 +0200323 return "No path could be found between {0} and {1}".format(vnf_src_name, vnf_dst_name)
324
peustermf9a817d2016-07-18 09:06:04 +0200325 LOG.info("Path between {0} and {1}: {2}".format(vnf_src_name, vnf_dst_name, path))
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100326
stevenvanrossem9315da42016-04-11 12:10:06 +0200327 current_hop = src_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200328 switch_inport_nr = src_sw_inport_nr
stevenvanrossem9315da42016-04-11 12:10:06 +0200329
stevenvanrossem461941c2016-05-10 11:41:29 +0200330 # choose free vlan if path contains more than 1 switch
stevenvanrossem27b6d952016-05-10 16:37:57 +0200331 cmd = kwargs.get('cmd')
332 vlan = None
333 if cmd == 'add-flow':
334 if len(path) > 1:
335 vlan = self.vlans.pop()
stevenvanrossem461941c2016-05-10 11:41:29 +0200336
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100337 for i in range(0,len(path)):
stevenvanrossem9315da42016-04-11 12:10:06 +0200338 current_node = self.getNodeByName(current_hop)
stevenvanrossem461941c2016-05-10 11:41:29 +0200339
stevenvanrossem9315da42016-04-11 12:10:06 +0200340 if path.index(current_hop) < len(path)-1:
341 next_hop = path[path.index(current_hop)+1]
342 else:
343 #last switch reached
344 next_hop = vnf_dst_name
345
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100346 next_node = self.getNodeByName(next_hop)
347
348 if next_hop == vnf_dst_name:
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200349 switch_outport_nr = dst_sw_outport_nr
peustermf9a817d2016-07-18 09:06:04 +0200350 LOG.info("end node reached: {0}".format(vnf_dst_name))
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100351 elif not isinstance( next_node, OVSSwitch ):
peustermf9a817d2016-07-18 09:06:04 +0200352 LOG.info("Next node: {0} is not a switch".format(next_hop))
stevenvanrossemeefea6c2016-02-17 12:03:26 +0100353 return "Next node: {0} is not a switch".format(next_hop)
stevenvanrossem9315da42016-04-11 12:10:06 +0200354 else:
stevenvanrossemed711fd2016-04-11 16:59:29 +0200355 # take first link between switches by default
stevenvanrossem9315da42016-04-11 12:10:06 +0200356 index_edge_out = 0
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200357 switch_outport_nr = self.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
stevenvanrossem9315da42016-04-11 12:10:06 +0200358
stevenvanrossem9315da42016-04-11 12:10:06 +0200359
stevenvanrossem461941c2016-05-10 11:41:29 +0200360 # set of entry via ovs-ofctl
stevenvanrossem9315da42016-04-11 12:10:06 +0200361 if isinstance( current_node, OVSSwitch ):
stevenvanrossem461941c2016-05-10 11:41:29 +0200362 kwargs['vlan'] = vlan
363 kwargs['path'] = path
364 kwargs['current_hop'] = current_hop
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200365
366 if self.controller == RemoteController:
367 ## set flow entry via ryu rest api
368 self._set_flow_entry_ryu_rest(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
369 else:
370 ## set flow entry via ovs-ofctl
371 self._set_flow_entry_dpctl(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
372
373
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100374
stevenvanrossemed711fd2016-04-11 16:59:29 +0200375 # take first link between switches by default
376 if isinstance( next_node, OVSSwitch ):
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200377 switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
stevenvanrossemed711fd2016-04-11 16:59:29 +0200378 current_hop = next_hop
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100379
stevenvanrossem27b6d952016-05-10 16:37:57 +0200380 return "path {2} between {0} and {1}".format(vnf_src_name, vnf_dst_name, cmd)
381
382 def _set_flow_entry_ryu_rest(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
383 match = 'in_port=%s' % switch_inport_nr
384
385 cookie = kwargs.get('cookie')
386 match_input = kwargs.get('match')
387 cmd = kwargs.get('cmd')
388 path = kwargs.get('path')
389 current_hop = kwargs.get('current_hop')
390 vlan = kwargs.get('vlan')
391
392 s = ','
393 if match_input:
394 match = s.join([match, match_input])
395
396 flow = {}
397 flow['dpid'] = int(node.dpid, 16)
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200398
stevenvanrossem27b6d952016-05-10 16:37:57 +0200399 if cookie:
400 flow['cookie'] = int(cookie)
401
402
403 flow['actions'] = []
404
405 # possible Ryu actions, match fields:
406 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#add-a-flow-entry
407 if cmd == 'add-flow':
408 prefix = 'stats/flowentry/add'
stevenvanrossem27b6d952016-05-10 16:37:57 +0200409 if vlan != None:
410 if path.index(current_hop) == 0: # first node
411 action = {}
412 action['type'] = 'PUSH_VLAN' # Push a new VLAN tag if a input frame is non-VLAN-tagged
413 action['ethertype'] = 33024 # Ethertype 0x8100(=33024): IEEE 802.1Q VLAN-tagged frame
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200414 flow['actions'].append(action)
415 action = {}
stevenvanrossem27b6d952016-05-10 16:37:57 +0200416 action['type'] = 'SET_FIELD'
417 action['field'] = 'vlan_vid'
418 action['value'] = vlan
419 flow['actions'].append(action)
420 elif path.index(current_hop) == len(path) - 1: # last node
421 match += ',dl_vlan=%s' % vlan
422 action = {}
423 action['type'] = 'POP_VLAN'
424 flow['actions'].append(action)
425 else: # middle nodes
426 match += ',dl_vlan=%s' % vlan
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200427 # output action must come last
428 action = {}
429 action['type'] = 'OUTPUT'
430 action['port'] = switch_outport_nr
431 flow['actions'].append(action)
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200432
stevenvanrossem27b6d952016-05-10 16:37:57 +0200433 elif cmd == 'del-flows':
stevenvanrossem27b6d952016-05-10 16:37:57 +0200434 prefix = 'stats/flowentry/delete'
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200435
stevenvanrossem27b6d952016-05-10 16:37:57 +0200436 if cookie:
stevenvanrossem1ef77022016-05-12 16:36:10 +0200437 # TODO: add cookie_mask as argument
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200438 flow['cookie_mask'] = int('0xffffffffffffffff', 16) # need full mask to match complete cookie
stevenvanrossem27b6d952016-05-10 16:37:57 +0200439
440 action = {}
441 action['type'] = 'OUTPUT'
442 action['port'] = switch_outport_nr
443 flow['actions'].append(action)
444
445 flow['match'] = self._parse_match(match)
446 self.ryu_REST(prefix, data=flow)
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100447
stevenvanrossem461941c2016-05-10 11:41:29 +0200448 def _set_flow_entry_dpctl(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
stevenvanrossem23c48092016-05-06 17:21:12 +0200449 match = 'in_port=%s' % switch_inport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200450
451 cookie = kwargs.get('cookie')
452 match_input = kwargs.get('match')
453 cmd = kwargs.get('cmd')
454 path = kwargs.get('path')
455 current_hop = kwargs.get('current_hop')
456 vlan = kwargs.get('vlan')
457
stevenvanrossem898a2af2016-05-06 18:28:57 +0200458 s = ','
459 if cookie:
460 cookie = 'cookie=%s' % cookie
461 match = s.join([cookie, match])
stevenvanrossem23c48092016-05-06 17:21:12 +0200462 if match_input:
stevenvanrossem23c48092016-05-06 17:21:12 +0200463 match = s.join([match, match_input])
stevenvanrossem23c48092016-05-06 17:21:12 +0200464 if cmd == 'add-flow':
465 action = 'action=%s' % switch_outport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200466 if vlan != None:
467 if path.index(current_hop) == 0: # first node
468 action = ('action=mod_vlan_vid:%s' % vlan) + (',output=%s' % switch_outport_nr)
469 match = '-O OpenFlow13 ' + match
470 elif path.index(current_hop) == len(path) - 1: # last node
471 match += ',dl_vlan=%s' % vlan
472 action = 'action=strip_vlan,output=%s' % switch_outport_nr
473 else: # middle nodes
474 match += ',dl_vlan=%s' % vlan
stevenvanrossem23c48092016-05-06 17:21:12 +0200475 ofcmd = s.join([match, action])
476 elif cmd == 'del-flows':
477 ofcmd = match
478 else:
479 ofcmd = ''
480
481 node.dpctl(cmd, ofcmd)
peustermf9a817d2016-07-18 09:06:04 +0200482 LOG.info("{3} in switch: {0} in_port: {1} out_port: {2}".format(node.name, switch_inport_nr,
stevenvanrossem461941c2016-05-10 11:41:29 +0200483 switch_outport_nr, cmd))
stevenvanrossem23c48092016-05-06 17:21:12 +0200484
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100485 # start Ryu Openflow controller as Remote Controller for the DCNetwork
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200486 def startRyu(self, learning_switch=True):
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100487 # start Ryu controller with rest-API
488 python_install_path = site.getsitepackages()[0]
489 ryu_path = python_install_path + '/ryu/app/simple_switch_13.py'
peustermde14f332016-03-15 16:14:21 +0100490 ryu_path2 = python_install_path + '/ryu/app/ofctl_rest.py'
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100491 # change the default Openflow controller port to 6653 (official IANA-assigned port number), as used by Mininet
492 # Ryu still uses 6633 as default
493 ryu_option = '--ofp-tcp-listen-port'
494 ryu_of_port = '6653'
peustermde14f332016-03-15 16:14:21 +0100495 ryu_cmd = 'ryu-manager'
peustermef6629e2016-03-14 17:21:56 +0100496 FNULL = open("/tmp/ryu.log", 'w')
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200497 if learning_switch:
498 self.ryu_process = Popen([ryu_cmd, ryu_path, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
499 else:
stevenvanrossem73efd192016-06-29 01:44:07 +0200500 # no learning switch, but with rest api
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200501 self.ryu_process = Popen([ryu_cmd, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
peusterm391773a2016-03-14 17:40:43 +0100502 time.sleep(1)
503
504 def stopRyu(self):
peustermde14f332016-03-15 16:14:21 +0100505 if self.ryu_process is not None:
peusterm391773a2016-03-14 17:40:43 +0100506 self.ryu_process.terminate()
507 self.ryu_process.kill()
508
stevenvanrossem27b6d952016-05-10 16:37:57 +0200509 def ryu_REST(self, prefix, dpid=None, data=None):
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200510 try:
511 if dpid:
512 url = self.ryu_REST_api + '/' + str(prefix) + '/' + str(dpid)
513 else:
514 url = self.ryu_REST_api + '/' + str(prefix)
515 if data:
peustermf9a817d2016-07-18 09:06:04 +0200516 #LOG.info('POST: {0}'.format(str(data)))
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200517 req = urllib2.Request(url, str(data))
518 else:
519 req = urllib2.Request(url)
stevenvanrossem27b6d952016-05-10 16:37:57 +0200520
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200521 ret = urllib2.urlopen(req).read()
522 return ret
523 except:
peustermf9a817d2016-07-18 09:06:04 +0200524 LOG.info('error url: {0}'.format(str(url)))
525 if data: LOG.info('error POST: {0}'.format(str(data)))
stevenvanrossem27b6d952016-05-10 16:37:57 +0200526
527 # need to respect that some match fields must be integers
528 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#description-of-match-and-actions
529 def _parse_match(self, match):
530 matches = match.split(',')
531 dict = {}
532 for m in matches:
533 match = m.split('=')
534 if len(match) == 2:
535 try:
536 m2 = int(match[1], 0)
537 except:
538 m2 = match[1]
539
540 dict.update({match[0]:m2})
541 return dict
542