blob: 4503e1c12806bec89a243729fb1971aa1d086fbf [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
stevenvanrossem3fc13932016-08-09 23:39:16 +020035import requests
stevenvanrossem9ebd0942016-02-22 10:13:05 +010036
peusterm5877ea22016-05-11 13:44:59 +020037from mininet.net import Containernet
peustermef6629e2016-03-14 17:21:56 +010038from mininet.node import Controller, DefaultController, OVSSwitch, OVSKernelSwitch, Docker, RemoteController
peustermcbcd4c22015-12-28 11:33:42 +010039from mininet.cli import CLI
peustermea8db832016-03-08 10:25:58 +010040from mininet.link import TCLink
peusterm8b04b532016-07-19 16:55:38 +020041from mininet.clean import cleanup
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 """
peusterm8b04b532016-07-19 16:55:38 +020069 # members
peustermcbcd4c22015-12-28 11:33:42 +010070 self.dcs = {}
peusterm8b04b532016-07-19 16:55:38 +020071 self.ryu_process = None
peusterm42f08be2016-03-10 21:56:34 +010072
peusterm8b04b532016-07-19 16:55:38 +020073 # always cleanup environment before we start the emulator
stevenvanrossem89706802016-07-19 02:54:45 +020074 self.killRyu()
peusterm8b04b532016-07-19 16:55:38 +020075 cleanup()
stevenvanrossem89706802016-07-19 02:54:45 +020076
peusterm293cbc32016-01-13 17:05:28 +010077 # call original Docker.__init__ and setup default controller
peusterm5877ea22016-05-11 13:44:59 +020078 Containernet.__init__(
stevenvanrossem7cd3c252016-05-11 22:55:15 +020079 self, switch=OVSKernelSwitch, controller=controller, **kwargs)
stevenvanrossemc5a536a2016-02-16 14:52:39 +010080
peustermde14f332016-03-15 16:14:21 +010081 # Ryu management
peustermde14f332016-03-15 16:14:21 +010082 if controller == RemoteController:
83 # start Ryu controller
stevenvanrossem7cd3c252016-05-11 22:55:15 +020084 self.startRyu(learning_switch=enable_learning)
stevenvanrossem9ebd0942016-02-22 10:13:05 +010085
peustermde14f332016-03-15 16:14:21 +010086 # add the specified controller
peustermef6629e2016-03-14 17:21:56 +010087 self.addController('c0', controller=controller)
88
89 # graph of the complete DC network
stevenvanrossemc1149022016-04-11 01:16:44 +020090 self.DCNetwork_graph = nx.MultiDiGraph()
peustermef6629e2016-03-14 17:21:56 +010091
stevenvanrossem461941c2016-05-10 11:41:29 +020092 # initialize pool of vlan tags to setup the SDN paths
93 self.vlans = range(4096)[::-1]
94
stevenvanrossem27b6d952016-05-10 16:37:57 +020095 # link to Ryu REST_API
96 ryu_ip = '0.0.0.0'
97 ryu_port = '8080'
98 self.ryu_REST_api = 'http://{0}:{1}'.format(ryu_ip, ryu_port)
99
peustermef6629e2016-03-14 17:21:56 +0100100 # monitoring agent
stevenvanrossem60670da2016-04-15 15:31:28 +0200101 if monitor:
102 self.monitor_agent = DCNetworkMonitor(self)
103 else:
104 self.monitor_agent = None
peustermef6629e2016-03-14 17:21:56 +0100105
peusterm42f08be2016-03-10 21:56:34 +0100106 # initialize resource model registrar
peusterma4d84792016-03-25 12:27:07 +0100107 self.rm_registrar = ResourceModelRegistrar(
108 dc_emulation_max_cpu, dc_emulation_max_mem)
peustermcbcd4c22015-12-28 11:33:42 +0100109
peusterm60bf8b82016-04-06 14:12:35 +0200110 def addDatacenter(self, label, metadata={}, resource_log_path=None):
peustermcbcd4c22015-12-28 11:33:42 +0100111 """
112 Create and add a logical cloud data center to the network.
113 """
peusterma47db032016-02-04 14:55:29 +0100114 if label in self.dcs:
115 raise Exception("Data center label already exists: %s" % label)
peusterm60bf8b82016-04-06 14:12:35 +0200116 dc = Datacenter(label, metadata=metadata, resource_log_path=resource_log_path)
peustermcbcd4c22015-12-28 11:33:42 +0100117 dc.net = self # set reference to network
peusterma47db032016-02-04 14:55:29 +0100118 self.dcs[label] = dc
peustermcbcd4c22015-12-28 11:33:42 +0100119 dc.create() # finally create the data center in our Mininet instance
peustermf9a817d2016-07-18 09:06:04 +0200120 LOG.info("added data center: %s" % label)
peustermcbcd4c22015-12-28 11:33:42 +0100121 return dc
122
peusterme6092692016-01-11 16:32:58 +0100123 def addLink(self, node1, node2, **params):
peusterm5b844a12016-01-11 15:58:15 +0100124 """
125 Able to handle Datacenter objects as link
126 end points.
127 """
peustermcbcd4c22015-12-28 11:33:42 +0100128 assert node1 is not None
129 assert node2 is not None
peustermf9a817d2016-07-18 09:06:04 +0200130 LOG.debug("addLink: n1=%s n2=%s" % (str(node1), str(node2)))
peustermcbcd4c22015-12-28 11:33:42 +0100131 # ensure type of node1
132 if isinstance( node1, basestring ):
133 if node1 in self.dcs:
134 node1 = self.dcs[node1].switch
peustermcbcd4c22015-12-28 11:33:42 +0100135 if isinstance( node1, Datacenter ):
136 node1 = node1.switch
137 # ensure type of node2
138 if isinstance( node2, basestring ):
139 if node2 in self.dcs:
140 node2 = self.dcs[node2].switch
peustermcbcd4c22015-12-28 11:33:42 +0100141 if isinstance( node2, Datacenter ):
142 node2 = node2.switch
peustermc3b977e2016-01-12 10:09:35 +0100143 # try to give containers a default IP
144 if isinstance( node1, Docker ):
peustermea8db832016-03-08 10:25:58 +0100145 if "params1" not in params:
peustermc3b977e2016-01-12 10:09:35 +0100146 params["params1"] = {}
peustermea8db832016-03-08 10:25:58 +0100147 if "ip" not in params["params1"]:
peustermc3b977e2016-01-12 10:09:35 +0100148 params["params1"]["ip"] = self.getNextIp()
149 if isinstance( node2, Docker ):
peustermea8db832016-03-08 10:25:58 +0100150 if "params2" not in params:
peustermc3b977e2016-01-12 10:09:35 +0100151 params["params2"] = {}
peustermea8db832016-03-08 10:25:58 +0100152 if "ip" not in params["params2"]:
peustermc3b977e2016-01-12 10:09:35 +0100153 params["params2"]["ip"] = self.getNextIp()
peustermea8db832016-03-08 10:25:58 +0100154 # ensure that we allow TCLinks between data centers
155 # TODO this is not optimal, we use cls=Link for containers and TCLink for data centers
peusterm5877ea22016-05-11 13:44:59 +0200156 # see Containernet issue: https://github.com/mpeuster/containernet/issues/3
peustermea8db832016-03-08 10:25:58 +0100157 if "cls" not in params:
158 params["cls"] = TCLink
peustermc3b977e2016-01-12 10:09:35 +0100159
peusterm5877ea22016-05-11 13:44:59 +0200160 link = Containernet.addLink(self, node1, node2, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100161
stevenvanrossemc1149022016-04-11 01:16:44 +0200162 # try to give container interfaces a default id
163 node1_port_id = node1.ports[link.intf1]
164 if isinstance(node1, Docker):
165 if "id" in params["params1"]:
166 node1_port_id = params["params1"]["id"]
stevenvanrossem5b376412016-05-04 15:34:49 +0200167 node1_port_name = link.intf1.name
stevenvanrossemc1149022016-04-11 01:16:44 +0200168
169 node2_port_id = node2.ports[link.intf2]
170 if isinstance(node2, Docker):
171 if "id" in params["params2"]:
172 node2_port_id = params["params2"]["id"]
stevenvanrossem5b376412016-05-04 15:34:49 +0200173 node2_port_name = link.intf2.name
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200174
175
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100176 # add edge and assigned port number to graph in both directions between node1 and node2
stevenvanrossemc1149022016-04-11 01:16:44 +0200177 # port_id: id given in descriptor (if available, otherwise same as port)
peusterm5877ea22016-05-11 13:44:59 +0200178 # port: portnumber assigned by Containernet
stevenvanrossemc1149022016-04-11 01:16:44 +0200179
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200180 attr_dict = {}
181 # possible weight metrics allowed by TClink class:
182 weight_metrics = ['bw', 'delay', 'jitter', 'loss']
183 edge_attributes = [p for p in params if p in weight_metrics]
184 for attr in edge_attributes:
185 # if delay: strip ms (need number as weight in graph)
186 match = re.search('([0-9]*\.?[0-9]+)', params[attr])
187 if match:
188 attr_number = match.group(1)
189 else:
190 attr_number = None
191 attr_dict[attr] = attr_number
192
193
stevenvanrossem5b376412016-05-04 15:34:49 +0200194 attr_dict2 = {'src_port_id': node1_port_id, 'src_port_nr': node1.ports[link.intf1],
195 'src_port_name': node1_port_name,
196 'dst_port_id': node2_port_id, 'dst_port_nr': node2.ports[link.intf2],
197 'dst_port_name': node2_port_name}
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200198 attr_dict2.update(attr_dict)
199 self.DCNetwork_graph.add_edge(node1.name, node2.name, attr_dict=attr_dict2)
200
stevenvanrossem5b376412016-05-04 15:34:49 +0200201 attr_dict2 = {'src_port_id': node2_port_id, 'src_port_nr': node2.ports[link.intf2],
202 'src_port_name': node2_port_name,
203 'dst_port_id': node1_port_id, 'dst_port_nr': node1.ports[link.intf1],
204 'dst_port_name': node1_port_name}
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200205 attr_dict2.update(attr_dict)
206 self.DCNetwork_graph.add_edge(node2.name, node1.name, attr_dict=attr_dict2)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100207
208 return link
peustermcbcd4c22015-12-28 11:33:42 +0100209
peusterma47db032016-02-04 14:55:29 +0100210 def addDocker( self, label, **params ):
peusterm5b844a12016-01-11 15:58:15 +0100211 """
peusterm293cbc32016-01-13 17:05:28 +0100212 Wrapper for addDocker method to use custom container class.
peusterm5b844a12016-01-11 15:58:15 +0100213 """
stevenvanrossem8fbf9782016-02-17 11:40:23 +0100214 self.DCNetwork_graph.add_node(label)
peusterm5877ea22016-05-11 13:44:59 +0200215 return Containernet.addDocker(self, label, cls=EmulatorCompute, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100216
stevenvanrossem8fbf9782016-02-17 11:40:23 +0100217 def removeDocker( self, label, **params ):
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100218 """
219 Wrapper for removeDocker method to update graph.
220 """
stevenvanrossem8fbf9782016-02-17 11:40:23 +0100221 self.DCNetwork_graph.remove_node(label)
peusterm5877ea22016-05-11 13:44:59 +0200222 return Containernet.removeDocker(self, label, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100223
224 def addSwitch( self, name, add_to_graph=True, **params ):
225 """
226 Wrapper for addSwitch method to store switch also in graph.
227 """
228 if add_to_graph:
229 self.DCNetwork_graph.add_node(name)
peusterm5877ea22016-05-11 13:44:59 +0200230 return Containernet.addSwitch(self, name, protocols='OpenFlow10,OpenFlow12,OpenFlow13', **params)
peustermc3b977e2016-01-12 10:09:35 +0100231
peustermbd44f4a2016-01-13 14:53:30 +0100232 def getAllContainers(self):
233 """
234 Returns a list with all containers within all data centers.
235 """
236 all_containers = []
237 for dc in self.dcs.itervalues():
238 all_containers += dc.listCompute()
239 return all_containers
240
peustermcbcd4c22015-12-28 11:33:42 +0100241 def start(self):
242 # start
243 for dc in self.dcs.itervalues():
244 dc.start()
peusterm5877ea22016-05-11 13:44:59 +0200245 Containernet.start(self)
peustermcbcd4c22015-12-28 11:33:42 +0100246
247 def stop(self):
stevenvanrossem60670da2016-04-15 15:31:28 +0200248
stevenvanrossemc6abf132016-04-14 11:15:58 +0200249 # stop the monitor agent
stevenvanrossem60670da2016-04-15 15:31:28 +0200250 if self.monitor_agent is not None:
251 self.monitor_agent.stop()
peustermcbcd4c22015-12-28 11:33:42 +0100252
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200253 # stop emulator net
peusterm5877ea22016-05-11 13:44:59 +0200254 Containernet.stop(self)
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200255
256 # stop Ryu controller
peusterm8b04b532016-07-19 16:55:38 +0200257 self.killRyu()
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200258
259
peustermcbcd4c22015-12-28 11:33:42 +0100260 def CLI(self):
peusterm293cbc32016-01-13 17:05:28 +0100261 CLI(self)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100262
263 # to remove chain do setChain( src, dst, cmd='del-flows')
stevenvanrossem461941c2016-05-10 11:41:29 +0200264 def setChain(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
265 cmd = kwargs.get('cmd')
266 if cmd == 'add-flow':
267 ret = self._chainAddFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface, **kwargs)
268 if kwargs.get('bidirectional'):
stevenvanrossem81955a52016-05-12 14:34:12 +0200269 ret = ret +'\n' + self._chainAddFlow(vnf_dst_name, vnf_src_name, vnf_dst_interface, vnf_src_interface, **kwargs)
stevenvanrossem9315da42016-04-11 12:10:06 +0200270
stevenvanrosseme131bf52016-07-14 11:42:09 +0200271 elif cmd == 'del-flows':
stevenvanrossem461941c2016-05-10 11:41:29 +0200272 ret = self._chainAddFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface, **kwargs)
273 if kwargs.get('bidirectional'):
stevenvanrossem81955a52016-05-12 14:34:12 +0200274 ret = ret + '\n' + self._chainAddFlow(vnf_dst_name, vnf_src_name, vnf_dst_interface, vnf_src_interface, **kwargs)
stevenvanrossem461941c2016-05-10 11:41:29 +0200275
276 else:
stevenvanrossem81955a52016-05-12 14:34:12 +0200277 ret = "Command unknown"
278
279 return ret
stevenvanrossem461941c2016-05-10 11:41:29 +0200280
281
282 def _chainAddFlow(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
283
peusterm53d3c142016-07-18 10:10:11 +0200284 src_sw = None
285 dst_sw = None
286 src_sw_inport_nr = 0
287 dst_sw_outport_nr = 0
288
289 LOG.debug("call chainAddFlow vnf_src_name=%r, vnf_src_interface=%r, vnf_dst_name=%r, vnf_dst_interface=%r",
290 vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface)
291
stevenvanrossem9315da42016-04-11 12:10:06 +0200292 #check if port is specified (vnf:port)
stevenvanrossemed711fd2016-04-11 16:59:29 +0200293 if vnf_src_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200294 # take first interface by default
295 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
296 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200297 vnf_src_interface = link_dict[0]['src_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200298
stevenvanrossem9315da42016-04-11 12:10:06 +0200299 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
300 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
301 for link in link_dict:
peusterm53d3c142016-07-18 10:10:11 +0200302 if (link_dict[link]['src_port_id'] == vnf_src_interface or
303 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 +0200304 # found the right link and connected switch
stevenvanrossem9315da42016-04-11 12:10:06 +0200305 src_sw = connected_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200306 src_sw_inport_nr = link_dict[link]['dst_port_nr']
stevenvanrossem9315da42016-04-11 12:10:06 +0200307 break
308
stevenvanrossemed711fd2016-04-11 16:59:29 +0200309 if vnf_dst_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200310 # take first interface by default
311 connected_sw = self.DCNetwork_graph.neighbors(vnf_dst_name)[0]
312 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200313 vnf_dst_interface = link_dict[0]['dst_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200314
315 vnf_dst_name = vnf_dst_name.split(':')[0]
316 for connected_sw in self.DCNetwork_graph.neighbors(vnf_dst_name):
317 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
318 for link in link_dict:
peusterm53d3c142016-07-18 10:10:11 +0200319 if link_dict[link]['dst_port_id'] == vnf_dst_interface or \
320 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 +0200321 # found the right link and connected switch
322 dst_sw = connected_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200323 dst_sw_outport_nr = link_dict[link]['src_port_nr']
stevenvanrossem9315da42016-04-11 12:10:06 +0200324 break
325
326
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100327 # get shortest path
stevenvanrossem9315da42016-04-11 12:10:06 +0200328 try:
stevenvanrossem1f68afb2016-05-02 15:36:04 +0200329 # returns the first found shortest path
330 # if all shortest paths are wanted, use: all_shortest_paths
stevenvanrossem461941c2016-05-10 11:41:29 +0200331 path = nx.shortest_path(self.DCNetwork_graph, src_sw, dst_sw, weight=kwargs.get('weight'))
stevenvanrossem9315da42016-04-11 12:10:06 +0200332 except:
peusterm53d3c142016-07-18 10:10:11 +0200333 LOG.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
334 vnf_src_name, vnf_dst_name, src_sw, dst_sw))
peustermf9a817d2016-07-18 09:06:04 +0200335 LOG.debug("Graph nodes: %r" % self.DCNetwork_graph.nodes())
336 LOG.debug("Graph edges: %r" % self.DCNetwork_graph.edges())
peusterm53d3c142016-07-18 10:10:11 +0200337 for e, v in self.DCNetwork_graph.edges():
338 LOG.debug("%r" % self.DCNetwork_graph[e][v])
stevenvanrossem9315da42016-04-11 12:10:06 +0200339 return "No path could be found between {0} and {1}".format(vnf_src_name, vnf_dst_name)
340
peustermf9a817d2016-07-18 09:06:04 +0200341 LOG.info("Path between {0} and {1}: {2}".format(vnf_src_name, vnf_dst_name, path))
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100342
stevenvanrossem9315da42016-04-11 12:10:06 +0200343 current_hop = src_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200344 switch_inport_nr = src_sw_inport_nr
stevenvanrossem9315da42016-04-11 12:10:06 +0200345
stevenvanrossem461941c2016-05-10 11:41:29 +0200346 # choose free vlan if path contains more than 1 switch
stevenvanrossem27b6d952016-05-10 16:37:57 +0200347 cmd = kwargs.get('cmd')
348 vlan = None
349 if cmd == 'add-flow':
350 if len(path) > 1:
351 vlan = self.vlans.pop()
stevenvanrossem461941c2016-05-10 11:41:29 +0200352
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100353 for i in range(0,len(path)):
stevenvanrossem9315da42016-04-11 12:10:06 +0200354 current_node = self.getNodeByName(current_hop)
stevenvanrossem461941c2016-05-10 11:41:29 +0200355
stevenvanrossem9315da42016-04-11 12:10:06 +0200356 if path.index(current_hop) < len(path)-1:
357 next_hop = path[path.index(current_hop)+1]
358 else:
359 #last switch reached
360 next_hop = vnf_dst_name
361
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100362 next_node = self.getNodeByName(next_hop)
363
364 if next_hop == vnf_dst_name:
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200365 switch_outport_nr = dst_sw_outport_nr
peustermf9a817d2016-07-18 09:06:04 +0200366 LOG.info("end node reached: {0}".format(vnf_dst_name))
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100367 elif not isinstance( next_node, OVSSwitch ):
peustermf9a817d2016-07-18 09:06:04 +0200368 LOG.info("Next node: {0} is not a switch".format(next_hop))
stevenvanrossemeefea6c2016-02-17 12:03:26 +0100369 return "Next node: {0} is not a switch".format(next_hop)
stevenvanrossem9315da42016-04-11 12:10:06 +0200370 else:
stevenvanrossemed711fd2016-04-11 16:59:29 +0200371 # take first link between switches by default
stevenvanrossem9315da42016-04-11 12:10:06 +0200372 index_edge_out = 0
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200373 switch_outport_nr = self.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
stevenvanrossem9315da42016-04-11 12:10:06 +0200374
stevenvanrossem9315da42016-04-11 12:10:06 +0200375
stevenvanrossem461941c2016-05-10 11:41:29 +0200376 # set of entry via ovs-ofctl
stevenvanrossem9315da42016-04-11 12:10:06 +0200377 if isinstance( current_node, OVSSwitch ):
stevenvanrossem461941c2016-05-10 11:41:29 +0200378 kwargs['vlan'] = vlan
379 kwargs['path'] = path
380 kwargs['current_hop'] = current_hop
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200381
382 if self.controller == RemoteController:
383 ## set flow entry via ryu rest api
384 self._set_flow_entry_ryu_rest(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
385 else:
386 ## set flow entry via ovs-ofctl
387 self._set_flow_entry_dpctl(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
388
389
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100390
stevenvanrossemed711fd2016-04-11 16:59:29 +0200391 # take first link between switches by default
392 if isinstance( next_node, OVSSwitch ):
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200393 switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
stevenvanrossemed711fd2016-04-11 16:59:29 +0200394 current_hop = next_hop
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100395
stevenvanrossem27b6d952016-05-10 16:37:57 +0200396 return "path {2} between {0} and {1}".format(vnf_src_name, vnf_dst_name, cmd)
397
398 def _set_flow_entry_ryu_rest(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
399 match = 'in_port=%s' % switch_inport_nr
400
401 cookie = kwargs.get('cookie')
402 match_input = kwargs.get('match')
403 cmd = kwargs.get('cmd')
404 path = kwargs.get('path')
405 current_hop = kwargs.get('current_hop')
406 vlan = kwargs.get('vlan')
stevenvanrossem61699eb2016-08-05 15:57:59 +0200407 priority = kwargs.get('priority')
stevenvanrossem27b6d952016-05-10 16:37:57 +0200408
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)
stevenvanrossem61699eb2016-08-05 15:57:59 +0200418 if priority:
419 flow['priority'] = int(priority)
stevenvanrossem27b6d952016-05-10 16:37:57 +0200420
421 flow['actions'] = []
422
423 # possible Ryu actions, match fields:
424 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#add-a-flow-entry
425 if cmd == 'add-flow':
426 prefix = 'stats/flowentry/add'
stevenvanrossem27b6d952016-05-10 16:37:57 +0200427 if vlan != None:
428 if path.index(current_hop) == 0: # first node
429 action = {}
430 action['type'] = 'PUSH_VLAN' # Push a new VLAN tag if a input frame is non-VLAN-tagged
431 action['ethertype'] = 33024 # Ethertype 0x8100(=33024): IEEE 802.1Q VLAN-tagged frame
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200432 flow['actions'].append(action)
433 action = {}
stevenvanrossem27b6d952016-05-10 16:37:57 +0200434 action['type'] = 'SET_FIELD'
435 action['field'] = 'vlan_vid'
436 action['value'] = vlan
437 flow['actions'].append(action)
438 elif path.index(current_hop) == len(path) - 1: # last node
439 match += ',dl_vlan=%s' % vlan
440 action = {}
441 action['type'] = 'POP_VLAN'
442 flow['actions'].append(action)
443 else: # middle nodes
444 match += ',dl_vlan=%s' % vlan
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200445 # output action must come last
446 action = {}
447 action['type'] = 'OUTPUT'
448 action['port'] = switch_outport_nr
449 flow['actions'].append(action)
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200450
stevenvanrossem27b6d952016-05-10 16:37:57 +0200451 elif cmd == 'del-flows':
stevenvanrossem27b6d952016-05-10 16:37:57 +0200452 prefix = 'stats/flowentry/delete'
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200453
stevenvanrossem27b6d952016-05-10 16:37:57 +0200454 if cookie:
stevenvanrossem1ef77022016-05-12 16:36:10 +0200455 # TODO: add cookie_mask as argument
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200456 flow['cookie_mask'] = int('0xffffffffffffffff', 16) # need full mask to match complete cookie
stevenvanrossem27b6d952016-05-10 16:37:57 +0200457
458 action = {}
459 action['type'] = 'OUTPUT'
460 action['port'] = switch_outport_nr
461 flow['actions'].append(action)
462
463 flow['match'] = self._parse_match(match)
464 self.ryu_REST(prefix, data=flow)
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100465
stevenvanrossem461941c2016-05-10 11:41:29 +0200466 def _set_flow_entry_dpctl(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
stevenvanrossem23c48092016-05-06 17:21:12 +0200467 match = 'in_port=%s' % switch_inport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200468
469 cookie = kwargs.get('cookie')
470 match_input = kwargs.get('match')
471 cmd = kwargs.get('cmd')
472 path = kwargs.get('path')
473 current_hop = kwargs.get('current_hop')
474 vlan = kwargs.get('vlan')
475
stevenvanrossem898a2af2016-05-06 18:28:57 +0200476 s = ','
477 if cookie:
478 cookie = 'cookie=%s' % cookie
479 match = s.join([cookie, match])
stevenvanrossem23c48092016-05-06 17:21:12 +0200480 if match_input:
stevenvanrossem23c48092016-05-06 17:21:12 +0200481 match = s.join([match, match_input])
stevenvanrossem23c48092016-05-06 17:21:12 +0200482 if cmd == 'add-flow':
483 action = 'action=%s' % switch_outport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200484 if vlan != None:
485 if path.index(current_hop) == 0: # first node
486 action = ('action=mod_vlan_vid:%s' % vlan) + (',output=%s' % switch_outport_nr)
487 match = '-O OpenFlow13 ' + match
488 elif path.index(current_hop) == len(path) - 1: # last node
489 match += ',dl_vlan=%s' % vlan
490 action = 'action=strip_vlan,output=%s' % switch_outport_nr
491 else: # middle nodes
492 match += ',dl_vlan=%s' % vlan
stevenvanrossem23c48092016-05-06 17:21:12 +0200493 ofcmd = s.join([match, action])
494 elif cmd == 'del-flows':
495 ofcmd = match
496 else:
497 ofcmd = ''
498
499 node.dpctl(cmd, ofcmd)
peustermf9a817d2016-07-18 09:06:04 +0200500 LOG.info("{3} in switch: {0} in_port: {1} out_port: {2}".format(node.name, switch_inport_nr,
stevenvanrossem461941c2016-05-10 11:41:29 +0200501 switch_outport_nr, cmd))
stevenvanrossem23c48092016-05-06 17:21:12 +0200502
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100503 # start Ryu Openflow controller as Remote Controller for the DCNetwork
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200504 def startRyu(self, learning_switch=True):
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100505 # start Ryu controller with rest-API
506 python_install_path = site.getsitepackages()[0]
507 ryu_path = python_install_path + '/ryu/app/simple_switch_13.py'
peustermde14f332016-03-15 16:14:21 +0100508 ryu_path2 = python_install_path + '/ryu/app/ofctl_rest.py'
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100509 # change the default Openflow controller port to 6653 (official IANA-assigned port number), as used by Mininet
510 # Ryu still uses 6633 as default
511 ryu_option = '--ofp-tcp-listen-port'
512 ryu_of_port = '6653'
peustermde14f332016-03-15 16:14:21 +0100513 ryu_cmd = 'ryu-manager'
peustermef6629e2016-03-14 17:21:56 +0100514 FNULL = open("/tmp/ryu.log", 'w')
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200515 if learning_switch:
516 self.ryu_process = Popen([ryu_cmd, ryu_path, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
517 else:
stevenvanrossem73efd192016-06-29 01:44:07 +0200518 # no learning switch, but with rest api
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200519 self.ryu_process = Popen([ryu_cmd, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
peusterm391773a2016-03-14 17:40:43 +0100520 time.sleep(1)
521
peusterm8b04b532016-07-19 16:55:38 +0200522 def killRyu(self):
523 """
524 Stop the Ryu controller that might be started by son-emu.
525 :return:
526 """
527 # try it nicely
peustermde14f332016-03-15 16:14:21 +0100528 if self.ryu_process is not None:
peusterm391773a2016-03-14 17:40:43 +0100529 self.ryu_process.terminate()
530 self.ryu_process.kill()
peusterm8b04b532016-07-19 16:55:38 +0200531 # ensure its death ;-)
stevenvanrossem89706802016-07-19 02:54:45 +0200532 Popen(['pkill', '-f', 'ryu-manager'])
peusterm391773a2016-03-14 17:40:43 +0100533
stevenvanrossem27b6d952016-05-10 16:37:57 +0200534 def ryu_REST(self, prefix, dpid=None, data=None):
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200535 try:
536 if dpid:
537 url = self.ryu_REST_api + '/' + str(prefix) + '/' + str(dpid)
538 else:
539 url = self.ryu_REST_api + '/' + str(prefix)
540 if data:
peustermf9a817d2016-07-18 09:06:04 +0200541 #LOG.info('POST: {0}'.format(str(data)))
stevenvanrossem3fc13932016-08-09 23:39:16 +0200542 #req = urllib2.Request(url, str(data))
543 req = requests.post(url, data=str(data))
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200544 else:
stevenvanrossem3fc13932016-08-09 23:39:16 +0200545 #req = urllib2.Request(url)
546 req = requests.get(url)
stevenvanrossem27b6d952016-05-10 16:37:57 +0200547
stevenvanrossem3fc13932016-08-09 23:39:16 +0200548 #ret = urllib2.urlopen(req).read()
549 ret = req.text
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200550 return ret
551 except:
peustermf9a817d2016-07-18 09:06:04 +0200552 LOG.info('error url: {0}'.format(str(url)))
553 if data: LOG.info('error POST: {0}'.format(str(data)))
stevenvanrossem27b6d952016-05-10 16:37:57 +0200554
555 # need to respect that some match fields must be integers
556 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#description-of-match-and-actions
557 def _parse_match(self, match):
558 matches = match.split(',')
559 dict = {}
560 for m in matches:
561 match = m.split('=')
562 if len(match) == 2:
563 try:
564 m2 = int(match[1], 0)
565 except:
566 m2 = match[1]
567
568 dict.update({match[0]:m2})
569 return dict
570