blob: a08a9e12c889040a5c9d05b094d265b3c5b52a62 [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
stevenvanrossem6b1d9b92016-05-02 13:10:40 +020033import re
stevenvanrossem3fc13932016-08-09 23:39:16 +020034import requests
stevenvanrossem9ebd0942016-02-22 10:13:05 +010035
peusterm5877ea22016-05-11 13:44:59 +020036from mininet.net import Containernet
peustermef6629e2016-03-14 17:21:56 +010037from mininet.node import Controller, DefaultController, OVSSwitch, OVSKernelSwitch, Docker, RemoteController
peustermcbcd4c22015-12-28 11:33:42 +010038from mininet.cli import CLI
peustermea8db832016-03-08 10:25:58 +010039from mininet.link import TCLink
peusterm8b04b532016-07-19 16:55:38 +020040from mininet.clean import cleanup
stevenvanrossemc5a536a2016-02-16 14:52:39 +010041import networkx as nx
cgeoffroy9524ad32016-03-03 18:24:15 +010042from emuvim.dcemulator.monitoring import DCNetworkMonitor
cgeoffroy9524ad32016-03-03 18:24:15 +010043from emuvim.dcemulator.node import Datacenter, EmulatorCompute
peusterm42f08be2016-03-10 21:56:34 +010044from emuvim.dcemulator.resourcemodel import ResourceModelRegistrar
peustermcbcd4c22015-12-28 11:33:42 +010045
peustermf9a817d2016-07-18 09:06:04 +020046LOG = logging.getLogger("dcemulator.net")
47LOG.setLevel(logging.DEBUG)
48
peusterm5877ea22016-05-11 13:44:59 +020049class DCNetwork(Containernet):
peusterme4e89d32016-01-07 09:14:54 +010050 """
peusterm5877ea22016-05-11 13:44:59 +020051 Wraps the original Mininet/Containernet class and provides
peusterme4e89d32016-01-07 09:14:54 +010052 methods to add data centers, switches, etc.
53
54 This class is used by topology definition scripts.
55 """
peustermcbcd4c22015-12-28 11:33:42 +010056
peusterm0ec25102016-04-16 02:16:20 +020057 def __init__(self, controller=RemoteController, monitor=False,
stevenvanrossem7cd3c252016-05-11 22:55:15 +020058 enable_learning = True, # in case of RemoteController (Ryu), learning switch behavior can be turned off/on
peusterma4d84792016-03-25 12:27:07 +010059 dc_emulation_max_cpu=1.0, # fraction of overall CPU time for emulation
60 dc_emulation_max_mem=512, # emulation max mem in MB
61 **kwargs):
peusterm42f08be2016-03-10 21:56:34 +010062 """
peusterm5877ea22016-05-11 13:44:59 +020063 Create an extended version of a Containernet network
peusterm42f08be2016-03-10 21:56:34 +010064 :param dc_emulation_max_cpu: max. CPU time used by containers in data centers
65 :param kwargs: path through for Mininet parameters
66 :return:
67 """
peusterm8b04b532016-07-19 16:55:38 +020068 # members
peustermcbcd4c22015-12-28 11:33:42 +010069 self.dcs = {}
peusterm8b04b532016-07-19 16:55:38 +020070 self.ryu_process = None
peusterm42f08be2016-03-10 21:56:34 +010071
peusterm8b04b532016-07-19 16:55:38 +020072 # always cleanup environment before we start the emulator
stevenvanrossem89706802016-07-19 02:54:45 +020073 self.killRyu()
peusterm8b04b532016-07-19 16:55:38 +020074 cleanup()
stevenvanrossem89706802016-07-19 02:54:45 +020075
peusterm293cbc32016-01-13 17:05:28 +010076 # call original Docker.__init__ and setup default controller
peusterm5877ea22016-05-11 13:44:59 +020077 Containernet.__init__(
stevenvanrossem7cd3c252016-05-11 22:55:15 +020078 self, switch=OVSKernelSwitch, controller=controller, **kwargs)
stevenvanrossemc5a536a2016-02-16 14:52:39 +010079
peustermde14f332016-03-15 16:14:21 +010080 # Ryu management
peustermde14f332016-03-15 16:14:21 +010081 if controller == RemoteController:
82 # start Ryu controller
stevenvanrossem7cd3c252016-05-11 22:55:15 +020083 self.startRyu(learning_switch=enable_learning)
stevenvanrossem9ebd0942016-02-22 10:13:05 +010084
peustermde14f332016-03-15 16:14:21 +010085 # add the specified controller
peustermef6629e2016-03-14 17:21:56 +010086 self.addController('c0', controller=controller)
87
88 # graph of the complete DC network
stevenvanrossemc1149022016-04-11 01:16:44 +020089 self.DCNetwork_graph = nx.MultiDiGraph()
peustermef6629e2016-03-14 17:21:56 +010090
stevenvanrossem461941c2016-05-10 11:41:29 +020091 # initialize pool of vlan tags to setup the SDN paths
92 self.vlans = range(4096)[::-1]
93
stevenvanrossem27b6d952016-05-10 16:37:57 +020094 # link to Ryu REST_API
stevenvanrossem51d4ae72016-08-10 13:22:53 +020095 ryu_ip = 'localhost'
stevenvanrossem27b6d952016-05-10 16:37:57 +020096 ryu_port = '8080'
97 self.ryu_REST_api = 'http://{0}:{1}'.format(ryu_ip, ryu_port)
stevenvanrossem51d4ae72016-08-10 13:22:53 +020098 self.RyuSession = requests.Session()
stevenvanrossem27b6d952016-05-10 16:37:57 +020099
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)
joka27edb4f2017-01-17 12:40:59 +0100186 match = re.search('([0-9]*\.?[0-9]+)', str(params[attr]))
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200187 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
stevenvanrossem461941c2016-05-10 11:41:29 +0200263 def setChain(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200264 """
265 Chain 2 vnf interfaces together by installing the flowrules in the switches along their path.
266 Currently the path is found using the default networkx shortest path function.
267 Each chain gets a unique vlan id , so different chains wil not interfere.
268
269 :param vnf_src_name: vnf name (string)
270 :param vnf_dst_name: vnf name (string)
271 :param vnf_src_interface: source interface name (string)
272 :param vnf_dst_interface: destination interface name (string)
273 :param cmd: 'add-flow' (default) to add a chain, 'del-flows' to remove a chain
274 :param cookie: cookie for the installed flowrules (can be used later as identifier for a set of installed chains)
275 :param match: custom match entry to be added to the flowrules (default: only in_port and vlan tag)
276 :param priority: custom flowrule priority
jokac304ad32017-01-09 10:58:23 +0100277 :param path: custom path between the two VNFs (list of switches)
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200278 :return: output log string
279 """
stevenvanrossem461941c2016-05-10 11:41:29 +0200280 cmd = kwargs.get('cmd')
281 if cmd == 'add-flow':
282 ret = self._chainAddFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface, **kwargs)
283 if kwargs.get('bidirectional'):
jokac304ad32017-01-09 10:58:23 +0100284 if kwargs.get('path') is not None:
285 kwargs['path'] = list(reversed(kwargs.get('path')))
stevenvanrossem81955a52016-05-12 14:34:12 +0200286 ret = ret +'\n' + self._chainAddFlow(vnf_dst_name, vnf_src_name, vnf_dst_interface, vnf_src_interface, **kwargs)
stevenvanrossem9315da42016-04-11 12:10:06 +0200287
stevenvanrosseme131bf52016-07-14 11:42:09 +0200288 elif cmd == 'del-flows':
stevenvanrossem461941c2016-05-10 11:41:29 +0200289 ret = self._chainAddFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface, **kwargs)
290 if kwargs.get('bidirectional'):
jokac304ad32017-01-09 10:58:23 +0100291 if kwargs.get('path') is not None:
292 kwargs['path'] = list(reversed(kwargs.get('path')))
stevenvanrossem81955a52016-05-12 14:34:12 +0200293 ret = ret + '\n' + self._chainAddFlow(vnf_dst_name, vnf_src_name, vnf_dst_interface, vnf_src_interface, **kwargs)
stevenvanrossem461941c2016-05-10 11:41:29 +0200294
295 else:
stevenvanrossem81955a52016-05-12 14:34:12 +0200296 ret = "Command unknown"
297
298 return ret
stevenvanrossem461941c2016-05-10 11:41:29 +0200299
300
301 def _chainAddFlow(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
302
peusterm53d3c142016-07-18 10:10:11 +0200303 src_sw = None
304 dst_sw = None
305 src_sw_inport_nr = 0
306 dst_sw_outport_nr = 0
307
308 LOG.debug("call chainAddFlow vnf_src_name=%r, vnf_src_interface=%r, vnf_dst_name=%r, vnf_dst_interface=%r",
309 vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface)
310
stevenvanrossem9315da42016-04-11 12:10:06 +0200311 #check if port is specified (vnf:port)
stevenvanrossemed711fd2016-04-11 16:59:29 +0200312 if vnf_src_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200313 # take first interface by default
314 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
315 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200316 vnf_src_interface = link_dict[0]['src_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200317
stevenvanrossem9315da42016-04-11 12:10:06 +0200318 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
319 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
320 for link in link_dict:
peusterm53d3c142016-07-18 10:10:11 +0200321 if (link_dict[link]['src_port_id'] == vnf_src_interface or
322 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 +0200323 # found the right link and connected switch
stevenvanrossem9315da42016-04-11 12:10:06 +0200324 src_sw = connected_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200325 src_sw_inport_nr = link_dict[link]['dst_port_nr']
stevenvanrossem9315da42016-04-11 12:10:06 +0200326 break
327
stevenvanrossemed711fd2016-04-11 16:59:29 +0200328 if vnf_dst_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200329 # take first interface by default
330 connected_sw = self.DCNetwork_graph.neighbors(vnf_dst_name)[0]
331 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200332 vnf_dst_interface = link_dict[0]['dst_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200333
334 vnf_dst_name = vnf_dst_name.split(':')[0]
335 for connected_sw in self.DCNetwork_graph.neighbors(vnf_dst_name):
336 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
337 for link in link_dict:
peusterm53d3c142016-07-18 10:10:11 +0200338 if link_dict[link]['dst_port_id'] == vnf_dst_interface or \
339 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 +0200340 # found the right link and connected switch
341 dst_sw = connected_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200342 dst_sw_outport_nr = link_dict[link]['src_port_nr']
stevenvanrossem9315da42016-04-11 12:10:06 +0200343 break
344
jokac304ad32017-01-09 10:58:23 +0100345 path = kwargs.get('path')
346 if path is None:
347 # get shortest path
348 try:
349 # returns the first found shortest path
350 # if all shortest paths are wanted, use: all_shortest_paths
351 path = nx.shortest_path(self.DCNetwork_graph, src_sw, dst_sw, weight=kwargs.get('weight'))
352 except:
353 LOG.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
354 vnf_src_name, vnf_dst_name, src_sw, dst_sw))
355 LOG.debug("Graph nodes: %r" % self.DCNetwork_graph.nodes())
356 LOG.debug("Graph edges: %r" % self.DCNetwork_graph.edges())
357 for e, v in self.DCNetwork_graph.edges():
358 LOG.debug("%r" % self.DCNetwork_graph[e][v])
359 return "No path could be found between {0} and {1}".format(vnf_src_name, vnf_dst_name)
stevenvanrossem9315da42016-04-11 12:10:06 +0200360
peustermf9a817d2016-07-18 09:06:04 +0200361 LOG.info("Path between {0} and {1}: {2}".format(vnf_src_name, vnf_dst_name, path))
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100362
stevenvanrossem9315da42016-04-11 12:10:06 +0200363 current_hop = src_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200364 switch_inport_nr = src_sw_inport_nr
stevenvanrossem9315da42016-04-11 12:10:06 +0200365
stevenvanrossem461941c2016-05-10 11:41:29 +0200366 # choose free vlan if path contains more than 1 switch
stevenvanrossem27b6d952016-05-10 16:37:57 +0200367 cmd = kwargs.get('cmd')
368 vlan = None
369 if cmd == 'add-flow':
370 if len(path) > 1:
371 vlan = self.vlans.pop()
stevenvanrossem461941c2016-05-10 11:41:29 +0200372
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100373 for i in range(0,len(path)):
stevenvanrossem9315da42016-04-11 12:10:06 +0200374 current_node = self.getNodeByName(current_hop)
stevenvanrossem461941c2016-05-10 11:41:29 +0200375
jokac304ad32017-01-09 10:58:23 +0100376 if i < len(path) - 1:
377 next_hop = path[i + 1]
stevenvanrossem9315da42016-04-11 12:10:06 +0200378 else:
jokac304ad32017-01-09 10:58:23 +0100379 # last switch reached
stevenvanrossem9315da42016-04-11 12:10:06 +0200380 next_hop = vnf_dst_name
381
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100382 next_node = self.getNodeByName(next_hop)
383
384 if next_hop == vnf_dst_name:
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200385 switch_outport_nr = dst_sw_outport_nr
peustermf9a817d2016-07-18 09:06:04 +0200386 LOG.info("end node reached: {0}".format(vnf_dst_name))
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100387 elif not isinstance( next_node, OVSSwitch ):
peustermf9a817d2016-07-18 09:06:04 +0200388 LOG.info("Next node: {0} is not a switch".format(next_hop))
stevenvanrossemeefea6c2016-02-17 12:03:26 +0100389 return "Next node: {0} is not a switch".format(next_hop)
stevenvanrossem9315da42016-04-11 12:10:06 +0200390 else:
stevenvanrossemed711fd2016-04-11 16:59:29 +0200391 # take first link between switches by default
stevenvanrossem9315da42016-04-11 12:10:06 +0200392 index_edge_out = 0
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200393 switch_outport_nr = self.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
stevenvanrossem9315da42016-04-11 12:10:06 +0200394
stevenvanrossem9315da42016-04-11 12:10:06 +0200395
stevenvanrossem461941c2016-05-10 11:41:29 +0200396 # set of entry via ovs-ofctl
stevenvanrossem9315da42016-04-11 12:10:06 +0200397 if isinstance( current_node, OVSSwitch ):
stevenvanrossem461941c2016-05-10 11:41:29 +0200398 kwargs['vlan'] = vlan
399 kwargs['path'] = path
jokac304ad32017-01-09 10:58:23 +0100400 kwargs['pathindex'] = i
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200401
402 if self.controller == RemoteController:
403 ## set flow entry via ryu rest api
404 self._set_flow_entry_ryu_rest(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
405 else:
406 ## set flow entry via ovs-ofctl
407 self._set_flow_entry_dpctl(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
408
409
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100410
stevenvanrossemed711fd2016-04-11 16:59:29 +0200411 # take first link between switches by default
412 if isinstance( next_node, OVSSwitch ):
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200413 switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
stevenvanrossemed711fd2016-04-11 16:59:29 +0200414 current_hop = next_hop
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100415
stevenvanrossem27b6d952016-05-10 16:37:57 +0200416 return "path {2} between {0} and {1}".format(vnf_src_name, vnf_dst_name, cmd)
417
418 def _set_flow_entry_ryu_rest(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
419 match = 'in_port=%s' % switch_inport_nr
420
421 cookie = kwargs.get('cookie')
422 match_input = kwargs.get('match')
423 cmd = kwargs.get('cmd')
424 path = kwargs.get('path')
jokac304ad32017-01-09 10:58:23 +0100425 index = kwargs.get('pathindex')
426
stevenvanrossem27b6d952016-05-10 16:37:57 +0200427 vlan = kwargs.get('vlan')
stevenvanrossem61699eb2016-08-05 15:57:59 +0200428 priority = kwargs.get('priority')
stevenvanrossem27b6d952016-05-10 16:37:57 +0200429
430 s = ','
431 if match_input:
432 match = s.join([match, match_input])
433
434 flow = {}
435 flow['dpid'] = int(node.dpid, 16)
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200436
stevenvanrossem27b6d952016-05-10 16:37:57 +0200437 if cookie:
438 flow['cookie'] = int(cookie)
stevenvanrossem61699eb2016-08-05 15:57:59 +0200439 if priority:
440 flow['priority'] = int(priority)
stevenvanrossem27b6d952016-05-10 16:37:57 +0200441
442 flow['actions'] = []
443
444 # possible Ryu actions, match fields:
445 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#add-a-flow-entry
446 if cmd == 'add-flow':
447 prefix = 'stats/flowentry/add'
stevenvanrossem27b6d952016-05-10 16:37:57 +0200448 if vlan != None:
jokac304ad32017-01-09 10:58:23 +0100449 if index == 0: # first node
stevenvanrossem27b6d952016-05-10 16:37:57 +0200450 action = {}
451 action['type'] = 'PUSH_VLAN' # Push a new VLAN tag if a input frame is non-VLAN-tagged
452 action['ethertype'] = 33024 # Ethertype 0x8100(=33024): IEEE 802.1Q VLAN-tagged frame
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200453 flow['actions'].append(action)
454 action = {}
stevenvanrossem27b6d952016-05-10 16:37:57 +0200455 action['type'] = 'SET_FIELD'
456 action['field'] = 'vlan_vid'
joka693d4342016-11-23 18:15:45 +0100457 # ryu expects the field to be masked
458 action['value'] = vlan | 0x1000
stevenvanrossem27b6d952016-05-10 16:37:57 +0200459 flow['actions'].append(action)
jokac304ad32017-01-09 10:58:23 +0100460 elif index == len(path) -1: # last node
stevenvanrossem27b6d952016-05-10 16:37:57 +0200461 match += ',dl_vlan=%s' % vlan
462 action = {}
463 action['type'] = 'POP_VLAN'
464 flow['actions'].append(action)
465 else: # middle nodes
466 match += ',dl_vlan=%s' % vlan
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200467 # output action must come last
468 action = {}
469 action['type'] = 'OUTPUT'
470 action['port'] = switch_outport_nr
471 flow['actions'].append(action)
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200472
stevenvanrossem27b6d952016-05-10 16:37:57 +0200473 elif cmd == 'del-flows':
stevenvanrossem27b6d952016-05-10 16:37:57 +0200474 prefix = 'stats/flowentry/delete'
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200475
stevenvanrossem27b6d952016-05-10 16:37:57 +0200476 if cookie:
stevenvanrossem1ef77022016-05-12 16:36:10 +0200477 # TODO: add cookie_mask as argument
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200478 flow['cookie_mask'] = int('0xffffffffffffffff', 16) # need full mask to match complete cookie
stevenvanrossem27b6d952016-05-10 16:37:57 +0200479
480 action = {}
481 action['type'] = 'OUTPUT'
482 action['port'] = switch_outport_nr
483 flow['actions'].append(action)
484
485 flow['match'] = self._parse_match(match)
486 self.ryu_REST(prefix, data=flow)
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100487
stevenvanrossem461941c2016-05-10 11:41:29 +0200488 def _set_flow_entry_dpctl(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
stevenvanrossem23c48092016-05-06 17:21:12 +0200489 match = 'in_port=%s' % switch_inport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200490
491 cookie = kwargs.get('cookie')
492 match_input = kwargs.get('match')
493 cmd = kwargs.get('cmd')
494 path = kwargs.get('path')
jokac304ad32017-01-09 10:58:23 +0100495 index = kwargs.get('pathindex')
stevenvanrossem461941c2016-05-10 11:41:29 +0200496 vlan = kwargs.get('vlan')
497
stevenvanrossem898a2af2016-05-06 18:28:57 +0200498 s = ','
499 if cookie:
500 cookie = 'cookie=%s' % cookie
501 match = s.join([cookie, match])
stevenvanrossem23c48092016-05-06 17:21:12 +0200502 if match_input:
stevenvanrossem23c48092016-05-06 17:21:12 +0200503 match = s.join([match, match_input])
stevenvanrossem23c48092016-05-06 17:21:12 +0200504 if cmd == 'add-flow':
505 action = 'action=%s' % switch_outport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200506 if vlan != None:
jokac304ad32017-01-09 10:58:23 +0100507 if index == 0: # first node
stevenvanrossem461941c2016-05-10 11:41:29 +0200508 action = ('action=mod_vlan_vid:%s' % vlan) + (',output=%s' % switch_outport_nr)
509 match = '-O OpenFlow13 ' + match
jokac304ad32017-01-09 10:58:23 +0100510 elif index == len(path) - 1: # last node
stevenvanrossem461941c2016-05-10 11:41:29 +0200511 match += ',dl_vlan=%s' % vlan
512 action = 'action=strip_vlan,output=%s' % switch_outport_nr
513 else: # middle nodes
514 match += ',dl_vlan=%s' % vlan
stevenvanrossem23c48092016-05-06 17:21:12 +0200515 ofcmd = s.join([match, action])
516 elif cmd == 'del-flows':
517 ofcmd = match
518 else:
519 ofcmd = ''
520
521 node.dpctl(cmd, ofcmd)
peustermf9a817d2016-07-18 09:06:04 +0200522 LOG.info("{3} in switch: {0} in_port: {1} out_port: {2}".format(node.name, switch_inport_nr,
stevenvanrossem461941c2016-05-10 11:41:29 +0200523 switch_outport_nr, cmd))
stevenvanrossem23c48092016-05-06 17:21:12 +0200524
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100525 # start Ryu Openflow controller as Remote Controller for the DCNetwork
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200526 def startRyu(self, learning_switch=True):
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100527 # start Ryu controller with rest-API
528 python_install_path = site.getsitepackages()[0]
529 ryu_path = python_install_path + '/ryu/app/simple_switch_13.py'
peustermde14f332016-03-15 16:14:21 +0100530 ryu_path2 = python_install_path + '/ryu/app/ofctl_rest.py'
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100531 # change the default Openflow controller port to 6653 (official IANA-assigned port number), as used by Mininet
532 # Ryu still uses 6633 as default
533 ryu_option = '--ofp-tcp-listen-port'
534 ryu_of_port = '6653'
peustermde14f332016-03-15 16:14:21 +0100535 ryu_cmd = 'ryu-manager'
peustermef6629e2016-03-14 17:21:56 +0100536 FNULL = open("/tmp/ryu.log", 'w')
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200537 if learning_switch:
538 self.ryu_process = Popen([ryu_cmd, ryu_path, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
539 else:
stevenvanrossem73efd192016-06-29 01:44:07 +0200540 # no learning switch, but with rest api
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200541 self.ryu_process = Popen([ryu_cmd, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
peusterm391773a2016-03-14 17:40:43 +0100542 time.sleep(1)
543
peusterm8b04b532016-07-19 16:55:38 +0200544 def killRyu(self):
545 """
546 Stop the Ryu controller that might be started by son-emu.
547 :return:
548 """
549 # try it nicely
peustermde14f332016-03-15 16:14:21 +0100550 if self.ryu_process is not None:
peusterm391773a2016-03-14 17:40:43 +0100551 self.ryu_process.terminate()
552 self.ryu_process.kill()
peusterm8b04b532016-07-19 16:55:38 +0200553 # ensure its death ;-)
stevenvanrossem89706802016-07-19 02:54:45 +0200554 Popen(['pkill', '-f', 'ryu-manager'])
peusterm391773a2016-03-14 17:40:43 +0100555
stevenvanrossem27b6d952016-05-10 16:37:57 +0200556 def ryu_REST(self, prefix, dpid=None, data=None):
stevenvanrossem27b6d952016-05-10 16:37:57 +0200557
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200558 if dpid:
559 url = self.ryu_REST_api + '/' + str(prefix) + '/' + str(dpid)
560 else:
561 url = self.ryu_REST_api + '/' + str(prefix)
562 if data:
563 req = self.RyuSession.post(url, json=data)
564 else:
565 req = self.RyuSession.get(url)
566
567
568 # do extra logging if status code is not 200 (OK)
569 if req.status_code is not requests.codes.ok:
570 logging.info(
571 'type {0} encoding: {1} text: {2} headers: {3} history: {4}'.format(req.headers['content-type'],
572 req.encoding, req.text,
573 req.headers, req.history))
574 LOG.info('url: {0}'.format(str(url)))
575 if data: LOG.info('POST: {0}'.format(str(data)))
576 LOG.info('status: {0} reason: {1}'.format(req.status_code, req.reason))
577
578
579 if 'json' in req.headers['content-type']:
580 ret = req.json()
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200581 return ret
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200582
583 ret = req.text.rstrip()
584 return ret
585
stevenvanrossem27b6d952016-05-10 16:37:57 +0200586
587 # need to respect that some match fields must be integers
588 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#description-of-match-and-actions
589 def _parse_match(self, match):
590 matches = match.split(',')
591 dict = {}
592 for m in matches:
593 match = m.split('=')
594 if len(match) == 2:
595 try:
596 m2 = int(match[1], 0)
597 except:
598 m2 = match[1]
599
600 dict.update({match[0]:m2})
601 return dict
602