blob: 58c2bff46e9e569a3228bb2832c39aabe09bccd7 [file] [log] [blame]
peusterm72f09882018-05-15 17:10:27 +02001# Copyright (c) 2015 SONATA-NFV and Paderborn University
2# ALL RIGHTS RESERVED.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16# Neither the name of the SONATA-NFV, Paderborn University
17# nor the names of its contributors may be used to endorse or promote
18# products derived from this software without specific prior written
19# permission.
20#
21# This work has been performed in the framework of the SONATA project,
22# funded by the European Commission under Grant number 671517 through
23# the Horizon 2020 and 5G-PPP programmes. The authors would like to
24# acknowledge the contributions of their colleagues of the SONATA
25# partner consortium (www.sonata-nfv.eu).
peustermcbcd4c22015-12-28 11:33:42 +010026import logging
peustermef6629e2016-03-14 17:21:56 +010027import time
stevenvanrossem6b1d9b92016-05-02 13:10:40 +020028import re
stevenvanrossem3fc13932016-08-09 23:39:16 +020029import requests
stevenvanrossemc3a344f2016-11-04 19:34:47 +010030import os
stevenvanrossemae588012017-06-06 10:33:19 +020031import json
peusterm9435e982019-03-19 11:10:38 +010032import networkx as nx
33from subprocess import Popen
34from gevent import monkey
peusterm5877ea22016-05-11 13:44:59 +020035from mininet.net import Containernet
peusterm72f09882018-05-15 17:10:27 +020036from mininet.node import OVSSwitch, OVSKernelSwitch, Docker, RemoteController
peustermcbcd4c22015-12-28 11:33:42 +010037from mininet.cli import CLI
peustermea8db832016-03-08 10:25:58 +010038from mininet.link import TCLink
peusterm8b04b532016-07-19 16:55:38 +020039from mininet.clean import cleanup
cgeoffroy9524ad32016-03-03 18:24:15 +010040from emuvim.dcemulator.monitoring import DCNetworkMonitor
peusterm72f09882018-05-15 17:10:27 +020041from emuvim.dcemulator.node import Datacenter, EmulatorCompute
peusterm42f08be2016-03-10 21:56:34 +010042from emuvim.dcemulator.resourcemodel import ResourceModelRegistrar
peustermcbcd4c22015-12-28 11:33:42 +010043
peusterm9435e982019-03-19 11:10:38 +010044# ensure correct functionality of all gevent based REST servers
45monkey.patch_all(sys=True)
46
47# setup logging
peustermf9a817d2016-07-18 09:06:04 +020048LOG = logging.getLogger("dcemulator.net")
49LOG.setLevel(logging.DEBUG)
50
stevenvanrossemb3f34172016-11-16 23:30:57 +010051# default CPU period used for cpu percentage-based cfs values (microseconds)
52CPU_PERIOD = 1000000
53
stevenvanrossemae588012017-06-06 10:33:19 +020054# default priority setting for added flow-rules
55DEFAULT_PRIORITY = 1000
56# default cookie number for new flow-rules
57DEFAULT_COOKIE = 10
58
peusterm72f09882018-05-15 17:10:27 +020059
peusterm5877ea22016-05-11 13:44:59 +020060class DCNetwork(Containernet):
peusterme4e89d32016-01-07 09:14:54 +010061 """
peusterm5877ea22016-05-11 13:44:59 +020062 Wraps the original Mininet/Containernet class and provides
peusterme4e89d32016-01-07 09:14:54 +010063 methods to add data centers, switches, etc.
64
65 This class is used by topology definition scripts.
66 """
peustermcbcd4c22015-12-28 11:33:42 +010067
peusterm0ec25102016-04-16 02:16:20 +020068 def __init__(self, controller=RemoteController, monitor=False,
peusterm72f09882018-05-15 17:10:27 +020069 enable_learning=False,
70 # learning switch behavior of the default ovs switches icw Ryu
71 # controller can be turned off/on, needed for E-LAN
72 # functionality
peusterma4d84792016-03-25 12:27:07 +010073 dc_emulation_max_cpu=1.0, # fraction of overall CPU time for emulation
74 dc_emulation_max_mem=512, # emulation max mem in MB
75 **kwargs):
peusterm42f08be2016-03-10 21:56:34 +010076 """
peusterm5877ea22016-05-11 13:44:59 +020077 Create an extended version of a Containernet network
peusterm42f08be2016-03-10 21:56:34 +010078 :param dc_emulation_max_cpu: max. CPU time used by containers in data centers
79 :param kwargs: path through for Mininet parameters
80 :return:
81 """
peusterm8b04b532016-07-19 16:55:38 +020082 # members
peustermcbcd4c22015-12-28 11:33:42 +010083 self.dcs = {}
peusterm8b04b532016-07-19 16:55:38 +020084 self.ryu_process = None
peusterm72f09882018-05-15 17:10:27 +020085 # list of deployed nsds.E_Lines and E_LANs (uploaded from the dummy
86 # gatekeeper)
stevenvanrossembecc7c52016-11-07 05:52:01 +010087 self.deployed_nsds = []
88 self.deployed_elines = []
89 self.deployed_elans = []
90 self.installed_chains = []
91
peusterm8b04b532016-07-19 16:55:38 +020092 # always cleanup environment before we start the emulator
stevenvanrossem89706802016-07-19 02:54:45 +020093 self.killRyu()
peusterm8b04b532016-07-19 16:55:38 +020094 cleanup()
stevenvanrossem89706802016-07-19 02:54:45 +020095
peusterm293cbc32016-01-13 17:05:28 +010096 # call original Docker.__init__ and setup default controller
peusterm5877ea22016-05-11 13:44:59 +020097 Containernet.__init__(
stevenvanrossem7cd3c252016-05-11 22:55:15 +020098 self, switch=OVSKernelSwitch, controller=controller, **kwargs)
stevenvanrossemc5a536a2016-02-16 14:52:39 +010099
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100100 # default switch configuration
101 enable_ryu_learning = False
peusterm72f09882018-05-15 17:10:27 +0200102 if enable_learning:
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100103 self.failMode = 'standalone'
104 enable_ryu_learning = True
105 else:
106 self.failMode = 'secure'
107
peustermde14f332016-03-15 16:14:21 +0100108 # Ryu management
peustermde14f332016-03-15 16:14:21 +0100109 if controller == RemoteController:
110 # start Ryu controller
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100111 self.startRyu(learning_switch=enable_ryu_learning)
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100112
peustermde14f332016-03-15 16:14:21 +0100113 # add the specified controller
peustermef6629e2016-03-14 17:21:56 +0100114 self.addController('c0', controller=controller)
115
116 # graph of the complete DC network
stevenvanrossemc1149022016-04-11 01:16:44 +0200117 self.DCNetwork_graph = nx.MultiDiGraph()
peustermef6629e2016-03-14 17:21:56 +0100118
stevenvanrossem461941c2016-05-10 11:41:29 +0200119 # initialize pool of vlan tags to setup the SDN paths
peusterm54ebd582017-07-19 12:11:55 -0700120 self.vlans = range(1, 4095)[::-1]
stevenvanrossem461941c2016-05-10 11:41:29 +0200121
stevenvanrossem27b6d952016-05-10 16:37:57 +0200122 # link to Ryu REST_API
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200123 ryu_ip = 'localhost'
stevenvanrossem27b6d952016-05-10 16:37:57 +0200124 ryu_port = '8080'
125 self.ryu_REST_api = 'http://{0}:{1}'.format(ryu_ip, ryu_port)
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200126 self.RyuSession = requests.Session()
stevenvanrossem27b6d952016-05-10 16:37:57 +0200127
peustermef6629e2016-03-14 17:21:56 +0100128 # monitoring agent
stevenvanrossem60670da2016-04-15 15:31:28 +0200129 if monitor:
130 self.monitor_agent = DCNetworkMonitor(self)
131 else:
132 self.monitor_agent = None
peustermef6629e2016-03-14 17:21:56 +0100133
peusterm42f08be2016-03-10 21:56:34 +0100134 # initialize resource model registrar
peusterma4d84792016-03-25 12:27:07 +0100135 self.rm_registrar = ResourceModelRegistrar(
136 dc_emulation_max_cpu, dc_emulation_max_mem)
stevenvanrossemb3f34172016-11-16 23:30:57 +0100137 self.cpu_period = CPU_PERIOD
peustermcbcd4c22015-12-28 11:33:42 +0100138
peusterm60bf8b82016-04-06 14:12:35 +0200139 def addDatacenter(self, label, metadata={}, resource_log_path=None):
peustermcbcd4c22015-12-28 11:33:42 +0100140 """
141 Create and add a logical cloud data center to the network.
142 """
peusterma47db032016-02-04 14:55:29 +0100143 if label in self.dcs:
144 raise Exception("Data center label already exists: %s" % label)
peusterm72f09882018-05-15 17:10:27 +0200145 dc = Datacenter(label, metadata=metadata,
146 resource_log_path=resource_log_path)
peustermcbcd4c22015-12-28 11:33:42 +0100147 dc.net = self # set reference to network
peusterma47db032016-02-04 14:55:29 +0100148 self.dcs[label] = dc
peustermcbcd4c22015-12-28 11:33:42 +0100149 dc.create() # finally create the data center in our Mininet instance
peustermf9a817d2016-07-18 09:06:04 +0200150 LOG.info("added data center: %s" % label)
peustermcbcd4c22015-12-28 11:33:42 +0100151 return dc
152
peusterme6092692016-01-11 16:32:58 +0100153 def addLink(self, node1, node2, **params):
peusterm5b844a12016-01-11 15:58:15 +0100154 """
155 Able to handle Datacenter objects as link
156 end points.
157 """
peustermcbcd4c22015-12-28 11:33:42 +0100158 assert node1 is not None
159 assert node2 is not None
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100160
peustermcbcd4c22015-12-28 11:33:42 +0100161 # ensure type of node1
peusterm72f09882018-05-15 17:10:27 +0200162 if isinstance(node1, basestring):
peustermcbcd4c22015-12-28 11:33:42 +0100163 if node1 in self.dcs:
164 node1 = self.dcs[node1].switch
peusterm72f09882018-05-15 17:10:27 +0200165 if isinstance(node1, Datacenter):
peustermcbcd4c22015-12-28 11:33:42 +0100166 node1 = node1.switch
167 # ensure type of node2
peusterm72f09882018-05-15 17:10:27 +0200168 if isinstance(node2, basestring):
peustermcbcd4c22015-12-28 11:33:42 +0100169 if node2 in self.dcs:
170 node2 = self.dcs[node2].switch
peusterm72f09882018-05-15 17:10:27 +0200171 if isinstance(node2, Datacenter):
peustermcbcd4c22015-12-28 11:33:42 +0100172 node2 = node2.switch
peustermc3b977e2016-01-12 10:09:35 +0100173 # try to give containers a default IP
peusterm72f09882018-05-15 17:10:27 +0200174 if isinstance(node1, Docker):
peustermea8db832016-03-08 10:25:58 +0100175 if "params1" not in params:
peustermc3b977e2016-01-12 10:09:35 +0100176 params["params1"] = {}
peustermea8db832016-03-08 10:25:58 +0100177 if "ip" not in params["params1"]:
peustermc3b977e2016-01-12 10:09:35 +0100178 params["params1"]["ip"] = self.getNextIp()
peusterm72f09882018-05-15 17:10:27 +0200179 if isinstance(node2, Docker):
peustermea8db832016-03-08 10:25:58 +0100180 if "params2" not in params:
peustermc3b977e2016-01-12 10:09:35 +0100181 params["params2"] = {}
peustermea8db832016-03-08 10:25:58 +0100182 if "ip" not in params["params2"]:
peustermc3b977e2016-01-12 10:09:35 +0100183 params["params2"]["ip"] = self.getNextIp()
peustermea8db832016-03-08 10:25:58 +0100184 # ensure that we allow TCLinks between data centers
185 # TODO this is not optimal, we use cls=Link for containers and TCLink for data centers
peusterm72f09882018-05-15 17:10:27 +0200186 # see Containernet issue:
187 # https://github.com/mpeuster/containernet/issues/3
peustermea8db832016-03-08 10:25:58 +0100188 if "cls" not in params:
189 params["cls"] = TCLink
peustermc3b977e2016-01-12 10:09:35 +0100190
peusterm5877ea22016-05-11 13:44:59 +0200191 link = Containernet.addLink(self, node1, node2, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100192
stevenvanrossemc1149022016-04-11 01:16:44 +0200193 # try to give container interfaces a default id
194 node1_port_id = node1.ports[link.intf1]
195 if isinstance(node1, Docker):
196 if "id" in params["params1"]:
197 node1_port_id = params["params1"]["id"]
stevenvanrossem5b376412016-05-04 15:34:49 +0200198 node1_port_name = link.intf1.name
stevenvanrossemc1149022016-04-11 01:16:44 +0200199
200 node2_port_id = node2.ports[link.intf2]
201 if isinstance(node2, Docker):
202 if "id" in params["params2"]:
203 node2_port_id = params["params2"]["id"]
stevenvanrossem5b376412016-05-04 15:34:49 +0200204 node2_port_name = link.intf2.name
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200205
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100206 # add edge and assigned port number to graph in both directions between node1 and node2
stevenvanrossemc1149022016-04-11 01:16:44 +0200207 # port_id: id given in descriptor (if available, otherwise same as port)
peusterm5877ea22016-05-11 13:44:59 +0200208 # port: portnumber assigned by Containernet
stevenvanrossemc1149022016-04-11 01:16:44 +0200209
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200210 attr_dict = {}
211 # possible weight metrics allowed by TClink class:
212 weight_metrics = ['bw', 'delay', 'jitter', 'loss']
213 edge_attributes = [p for p in params if p in weight_metrics]
214 for attr in edge_attributes:
215 # if delay: strip ms (need number as weight in graph)
joka27edb4f2017-01-17 12:40:59 +0100216 match = re.search('([0-9]*\.?[0-9]+)', str(params[attr]))
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200217 if match:
218 attr_number = match.group(1)
219 else:
220 attr_number = None
221 attr_dict[attr] = attr_number
222
stevenvanrossem5b376412016-05-04 15:34:49 +0200223 attr_dict2 = {'src_port_id': node1_port_id, 'src_port_nr': node1.ports[link.intf1],
224 'src_port_name': node1_port_name,
peusterm72f09882018-05-15 17:10:27 +0200225 'dst_port_id': node2_port_id, 'dst_port_nr': node2.ports[link.intf2],
stevenvanrossem5b376412016-05-04 15:34:49 +0200226 'dst_port_name': node2_port_name}
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200227 attr_dict2.update(attr_dict)
peusterm72f09882018-05-15 17:10:27 +0200228 self.DCNetwork_graph.add_edge(
229 node1.name, node2.name, attr_dict=attr_dict2)
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200230
stevenvanrossem5b376412016-05-04 15:34:49 +0200231 attr_dict2 = {'src_port_id': node2_port_id, 'src_port_nr': node2.ports[link.intf2],
232 'src_port_name': node2_port_name,
peusterm72f09882018-05-15 17:10:27 +0200233 'dst_port_id': node1_port_id, 'dst_port_nr': node1.ports[link.intf1],
stevenvanrossem5b376412016-05-04 15:34:49 +0200234 'dst_port_name': node1_port_name}
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200235 attr_dict2.update(attr_dict)
peusterm72f09882018-05-15 17:10:27 +0200236 self.DCNetwork_graph.add_edge(
237 node2.name, node1.name, attr_dict=attr_dict2)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100238
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100239 LOG.debug("addLink: n1={0} intf1={1} -- n2={2} intf2={3}".format(
peusterm72f09882018-05-15 17:10:27 +0200240 str(node1), node1_port_name, str(node2), node2_port_name))
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100241
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100242 return link
peustermcbcd4c22015-12-28 11:33:42 +0100243
stevenvanrossem00e65b92017-04-18 16:57:40 +0200244 def removeLink(self, link=None, node1=None, node2=None):
245 """
246 Remove the link from the Containernet and the networkx graph
247 """
peusterm92a37cd2017-05-17 09:51:05 +0200248 if link is not None:
249 node1 = link.intf1.node
250 node2 = link.intf2.node
251 assert node1 is not None
252 assert node2 is not None
stevenvanrossem00e65b92017-04-18 16:57:40 +0200253 Containernet.removeLink(self, link=link, node1=node1, node2=node2)
peusterm92a37cd2017-05-17 09:51:05 +0200254 # TODO we might decrease the loglevel to debug:
255 try:
256 self.DCNetwork_graph.remove_edge(node2.name, node1.name)
peusterm72f09882018-05-15 17:10:27 +0200257 except BaseException:
258 LOG.warning("%s, %s not found in DCNetwork_graph." %
259 ((node2.name, node1.name)))
peusterm92a37cd2017-05-17 09:51:05 +0200260 try:
261 self.DCNetwork_graph.remove_edge(node1.name, node2.name)
peusterm72f09882018-05-15 17:10:27 +0200262 except BaseException:
263 LOG.warning("%s, %s not found in DCNetwork_graph." %
264 ((node1.name, node2.name)))
stevenvanrossem00e65b92017-04-18 16:57:40 +0200265
peusterm72f09882018-05-15 17:10:27 +0200266 def addDocker(self, label, **params):
peusterm5b844a12016-01-11 15:58:15 +0100267 """
peusterm293cbc32016-01-13 17:05:28 +0100268 Wrapper for addDocker method to use custom container class.
peusterm5b844a12016-01-11 15:58:15 +0100269 """
stevenvanrossemf3712012017-05-04 00:01:52 +0200270 self.DCNetwork_graph.add_node(label, type=params.get('type', 'docker'))
peusterm72f09882018-05-15 17:10:27 +0200271 return Containernet.addDocker(
272 self, label, cls=EmulatorCompute, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100273
peusterm72f09882018-05-15 17:10:27 +0200274 def removeDocker(self, label, **params):
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100275 """
276 Wrapper for removeDocker method to update graph.
277 """
stevenvanrossem8fbf9782016-02-17 11:40:23 +0100278 self.DCNetwork_graph.remove_node(label)
peusterm5877ea22016-05-11 13:44:59 +0200279 return Containernet.removeDocker(self, label, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100280
stevenvanrossemf3712012017-05-04 00:01:52 +0200281 def addExtSAP(self, sap_name, sap_ip, **params):
282 """
283 Wrapper for addExtSAP method to store SAP also in graph.
284 """
285 # make sure that 'type' is set
peusterm72f09882018-05-15 17:10:27 +0200286 params['type'] = params.get('type', 'sap_ext')
stevenvanrossemf3712012017-05-04 00:01:52 +0200287 self.DCNetwork_graph.add_node(sap_name, type=params['type'])
stevenvanrossemf3712012017-05-04 00:01:52 +0200288 return Containernet.addExtSAP(self, sap_name, sap_ip, **params)
289
290 def removeExtSAP(self, sap_name, **params):
291 """
292 Wrapper for removeExtSAP method to remove SAP also from graph.
293 """
294 self.DCNetwork_graph.remove_node(sap_name)
295 return Containernet.removeExtSAP(self, sap_name)
296
peusterm72f09882018-05-15 17:10:27 +0200297 def addSwitch(self, name, add_to_graph=True, **params):
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100298 """
299 Wrapper for addSwitch method to store switch also in graph.
300 """
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100301
302 # add this switch to the global topology overview
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100303 if add_to_graph:
peusterm72f09882018-05-15 17:10:27 +0200304 self.DCNetwork_graph.add_node(
305 name, type=params.get('type', 'switch'))
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100306
307 # set the learning switch behavior
peusterm72f09882018-05-15 17:10:27 +0200308 if 'failMode' in params:
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100309 failMode = params['failMode']
peusterm72f09882018-05-15 17:10:27 +0200310 else:
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100311 failMode = self.failMode
312
peusterm72f09882018-05-15 17:10:27 +0200313 s = Containernet.addSwitch(
314 self, name, protocols='OpenFlow10,OpenFlow12,OpenFlow13', failMode=failMode, **params)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100315
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100316 return s
peustermc3b977e2016-01-12 10:09:35 +0100317
peustermbd44f4a2016-01-13 14:53:30 +0100318 def getAllContainers(self):
319 """
320 Returns a list with all containers within all data centers.
321 """
322 all_containers = []
323 for dc in self.dcs.itervalues():
324 all_containers += dc.listCompute()
325 return all_containers
326
peustermcbcd4c22015-12-28 11:33:42 +0100327 def start(self):
328 # start
329 for dc in self.dcs.itervalues():
330 dc.start()
peusterm5877ea22016-05-11 13:44:59 +0200331 Containernet.start(self)
peustermcbcd4c22015-12-28 11:33:42 +0100332
333 def stop(self):
stevenvanrossem60670da2016-04-15 15:31:28 +0200334
stevenvanrossemc6abf132016-04-14 11:15:58 +0200335 # stop the monitor agent
stevenvanrossem60670da2016-04-15 15:31:28 +0200336 if self.monitor_agent is not None:
337 self.monitor_agent.stop()
peustermcbcd4c22015-12-28 11:33:42 +0100338
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200339 # stop emulator net
peusterm5877ea22016-05-11 13:44:59 +0200340 Containernet.stop(self)
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200341
342 # stop Ryu controller
peusterm8b04b532016-07-19 16:55:38 +0200343 self.killRyu()
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200344
peustermcbcd4c22015-12-28 11:33:42 +0100345 def CLI(self):
peusterm293cbc32016-01-13 17:05:28 +0100346 CLI(self)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100347
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100348 def setLAN(self, vnf_list):
349 """
350 setup an E-LAN network by assigning the same VLAN tag to each DC interface of the VNFs in the E-LAN
351
352 :param vnf_list: names of the VNFs in this E-LAN [{name:,interface:},...]
353 :return:
354 """
355 src_sw = None
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100356 src_sw_inport_name = None
357
358 # get a vlan tag for this E-LAN
359 vlan = self.vlans.pop()
360
361 for vnf in vnf_list:
362 vnf_src_name = vnf['name']
363 vnf_src_interface = vnf['interface']
364
365 # check if port is specified (vnf:port)
366 if vnf_src_interface is None:
367 # take first interface by default
368 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
369 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
370 vnf_src_interface = link_dict[0]['src_port_id']
371
372 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
373 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
374 for link in link_dict:
375 if (link_dict[link]['src_port_id'] == vnf_src_interface or
peusterm72f09882018-05-15 17:10:27 +0200376 link_dict[link]['src_port_name'] == vnf_src_interface): # Fix: we might also get interface names, e.g, from a son-emu-cli call
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100377 # found the right link and connected switch
378 src_sw = connected_sw
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100379 src_sw_inport_name = link_dict[link]['dst_port_name']
380 break
381
382 # set the tag on the dc switch interface
peusterm72f09882018-05-15 17:10:27 +0200383 LOG.debug('set E-LAN: vnf name: {0} interface: {1} tag: {2}'.format(
384 vnf_src_name, vnf_src_interface, vlan))
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100385 switch_node = self.getNodeByName(src_sw)
386 self._set_vlan_tag(switch_node, src_sw_inport_name, vlan)
387
stevenvanrossembecc7c52016-11-07 05:52:01 +0100388 def _addMonitorFlow(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None,
peusterm72f09882018-05-15 17:10:27 +0200389 tag=None, **kwargs):
stevenvanrossembf1754e2016-11-17 10:20:52 +0100390 """
391 Add a monitoring flow entry that adds a special flowentry/counter at the begin or end of a chain.
392 So this monitoring flowrule exists on top of a previously defined chain rule and uses the same vlan tag/routing.
393 :param vnf_src_name:
394 :param vnf_dst_name:
395 :param vnf_src_interface:
396 :param vnf_dst_interface:
397 :param tag: vlan tag to be used for this chain (same tag as existing chain)
398 :param monitor_placement: 'tx' or 'rx' indicating to place the extra flowentry resp. at the beginning or end of the chain
399 :return:
400 """
stevenvanrossembecc7c52016-11-07 05:52:01 +0100401
402 src_sw = None
403 src_sw_inport_nr = 0
404 src_sw_inport_name = None
405 dst_sw = None
406 dst_sw_outport_nr = 0
407 dst_sw_outport_name = None
408
409 LOG.debug("call AddMonitorFlow vnf_src_name=%r, vnf_src_interface=%r, vnf_dst_name=%r, vnf_dst_interface=%r",
410 vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface)
411
peusterm72f09882018-05-15 17:10:27 +0200412 # check if port is specified (vnf:port)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100413 if vnf_src_interface is None:
414 # take first interface by default
415 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
416 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
417 vnf_src_interface = link_dict[0]['src_port_id']
418
419 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
420 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
421 for link in link_dict:
422 if (link_dict[link]['src_port_id'] == vnf_src_interface or
423 link_dict[link]['src_port_name'] == vnf_src_interface): # Fix: we might also get interface names, e.g, from a son-emu-cli call
424 # found the right link and connected switch
425 src_sw = connected_sw
426 src_sw_inport_nr = link_dict[link]['dst_port_nr']
427 src_sw_inport_name = link_dict[link]['dst_port_name']
428 break
429
430 if vnf_dst_interface is None:
431 # take first interface by default
432 connected_sw = self.DCNetwork_graph.neighbors(vnf_dst_name)[0]
433 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
434 vnf_dst_interface = link_dict[0]['dst_port_id']
435
436 vnf_dst_name = vnf_dst_name.split(':')[0]
437 for connected_sw in self.DCNetwork_graph.neighbors(vnf_dst_name):
438 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
439 for link in link_dict:
440 if link_dict[link]['dst_port_id'] == vnf_dst_interface or \
441 link_dict[link]['dst_port_name'] == vnf_dst_interface: # Fix: we might also get interface names, e.g, from a son-emu-cli call
442 # found the right link and connected switch
443 dst_sw = connected_sw
444 dst_sw_outport_nr = link_dict[link]['src_port_nr']
445 dst_sw_outport_name = link_dict[link]['src_port_name']
446 break
447
448 if not tag >= 0:
449 LOG.exception('tag not valid: {0}'.format(tag))
450
451 # get shortest path
452 try:
453 # returns the first found shortest path
454 # if all shortest paths are wanted, use: all_shortest_paths
peusterm72f09882018-05-15 17:10:27 +0200455 path = nx.shortest_path(
456 self.DCNetwork_graph, src_sw, dst_sw, weight=kwargs.get('weight'))
457 except BaseException:
stevenvanrossembecc7c52016-11-07 05:52:01 +0100458 LOG.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
459 vnf_src_name, vnf_dst_name, src_sw, dst_sw))
460 LOG.debug("Graph nodes: %r" % self.DCNetwork_graph.nodes())
461 LOG.debug("Graph edges: %r" % self.DCNetwork_graph.edges())
462 for e, v in self.DCNetwork_graph.edges():
463 LOG.debug("%r" % self.DCNetwork_graph[e][v])
peusterm72f09882018-05-15 17:10:27 +0200464 return "No path could be found between {0} and {1}".format(
465 vnf_src_name, vnf_dst_name)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100466
peusterm37911562018-10-18 15:03:55 +0200467 LOG.debug("Creating path between {0} and {1}: {2}".format(
peusterm72f09882018-05-15 17:10:27 +0200468 vnf_src_name, vnf_dst_name, path))
stevenvanrossembecc7c52016-11-07 05:52:01 +0100469
470 current_hop = src_sw
471 switch_inport_nr = src_sw_inport_nr
472
473 cmd = kwargs.get('cmd')
474
peusterm72f09882018-05-15 17:10:27 +0200475 # iterate through the path to install the flow-entries
476 for i in range(0, len(path)):
stevenvanrossembecc7c52016-11-07 05:52:01 +0100477 current_node = self.getNodeByName(current_hop)
478
peusterm72f09882018-05-15 17:10:27 +0200479 if path.index(current_hop) < len(path) - 1:
480 next_hop = path[path.index(current_hop) + 1]
stevenvanrossembecc7c52016-11-07 05:52:01 +0100481 else:
peusterm72f09882018-05-15 17:10:27 +0200482 # last switch reached
stevenvanrossembecc7c52016-11-07 05:52:01 +0100483 next_hop = vnf_dst_name
484
485 next_node = self.getNodeByName(next_hop)
486
487 if next_hop == vnf_dst_name:
488 switch_outport_nr = dst_sw_outport_nr
peusterm37911562018-10-18 15:03:55 +0200489 LOG.debug("end node reached: {0}".format(vnf_dst_name))
peusterm72f09882018-05-15 17:10:27 +0200490 elif not isinstance(next_node, OVSSwitch):
stevenvanrossembecc7c52016-11-07 05:52:01 +0100491 LOG.info("Next node: {0} is not a switch".format(next_hop))
492 return "Next node: {0} is not a switch".format(next_hop)
493 else:
494 # take first link between switches by default
495 index_edge_out = 0
496 switch_outport_nr = self.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100497
peusterm72f09882018-05-15 17:10:27 +0200498 # set of entry via ovs-ofctl
499 if isinstance(current_node, OVSSwitch):
stevenvanrossembecc7c52016-11-07 05:52:01 +0100500 kwargs['vlan'] = tag
501 kwargs['path'] = path
502 kwargs['current_hop'] = current_hop
503 kwargs['switch_inport_name'] = src_sw_inport_name
504 kwargs['switch_outport_name'] = dst_sw_outport_name
505 kwargs['skip_vlan_tag'] = True
stevenvanrossem263eee52017-02-08 01:04:36 +0100506 kwargs['pathindex'] = i
stevenvanrossembecc7c52016-11-07 05:52:01 +0100507
stevenvanrossem4fac2af2016-12-22 01:26:02 +0100508 monitor_placement = kwargs.get('monitor_placement').strip()
stevenvanrossembecc7c52016-11-07 05:52:01 +0100509 # put monitor flow at the dst switch
510 insert_flow = False
peusterm72f09882018-05-15 17:10:27 +0200511 # first node:
512 if monitor_placement == 'tx' and path.index(current_hop) == 0:
stevenvanrossembecc7c52016-11-07 05:52:01 +0100513 insert_flow = True
514 # put monitoring flow at the src switch
peusterm72f09882018-05-15 17:10:27 +0200515 # last node:
516 elif monitor_placement == 'rx' and path.index(current_hop) == len(path) - 1:
stevenvanrossembecc7c52016-11-07 05:52:01 +0100517 insert_flow = True
stevenvanrossem4fac2af2016-12-22 01:26:02 +0100518 elif monitor_placement not in ['rx', 'tx']:
peusterm72f09882018-05-15 17:10:27 +0200519 LOG.exception(
520 'invalid monitor command: {0}'.format(monitor_placement))
stevenvanrossembecc7c52016-11-07 05:52:01 +0100521
522 if self.controller == RemoteController and insert_flow:
peusterm72f09882018-05-15 17:10:27 +0200523 # set flow entry via ryu rest api
524 self._set_flow_entry_ryu_rest(
525 current_node, switch_inport_nr, switch_outport_nr, **kwargs)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100526 break
527 elif insert_flow:
peusterm72f09882018-05-15 17:10:27 +0200528 # set flow entry via ovs-ofctl
529 self._set_flow_entry_dpctl(
530 current_node, switch_inport_nr, switch_outport_nr, **kwargs)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100531 break
532
533 # take first link between switches by default
peusterm72f09882018-05-15 17:10:27 +0200534 if isinstance(next_node, OVSSwitch):
stevenvanrossembecc7c52016-11-07 05:52:01 +0100535 switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
536 current_hop = next_hop
537
peusterm72f09882018-05-15 17:10:27 +0200538 return "path {2} between {0} and {1}".format(
539 vnf_src_name, vnf_dst_name, cmd)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100540
peusterm72f09882018-05-15 17:10:27 +0200541 def setChain(self, vnf_src_name, vnf_dst_name,
542 vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200543 """
544 Chain 2 vnf interfaces together by installing the flowrules in the switches along their path.
545 Currently the path is found using the default networkx shortest path function.
546 Each chain gets a unique vlan id , so different chains wil not interfere.
547
548 :param vnf_src_name: vnf name (string)
549 :param vnf_dst_name: vnf name (string)
550 :param vnf_src_interface: source interface name (string)
551 :param vnf_dst_interface: destination interface name (string)
552 :param cmd: 'add-flow' (default) to add a chain, 'del-flows' to remove a chain
553 :param cookie: cookie for the installed flowrules (can be used later as identifier for a set of installed chains)
554 :param match: custom match entry to be added to the flowrules (default: only in_port and vlan tag)
555 :param priority: custom flowrule priority
stevenvanrossembf1754e2016-11-17 10:20:52 +0100556 :param monitor: boolean to indicate whether this chain is a monitoring chain
557 :param tag: vlan tag to be used for this chain (pre-defined or new one if none is specified)
stevenvanrossem634c5ef2017-02-08 00:46:02 +0100558 :param skip_vlan_tag: boolean to indicate if a vlan tag should be appointed to this flow or not
jokac304ad32017-01-09 10:58:23 +0100559 :param path: custom path between the two VNFs (list of switches)
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200560 :return: output log string
561 """
stevenvanrossembecc7c52016-11-07 05:52:01 +0100562
563 # special procedure for monitoring flows
564 if kwargs.get('monitor'):
565
566 # check if chain already exists
567 found_chains = [chain_dict for chain_dict in self.installed_chains if
peusterm72f09882018-05-15 17:10:27 +0200568 (chain_dict['vnf_src_name'] == vnf_src_name and
569 chain_dict['vnf_src_interface'] == vnf_src_interface and
570 chain_dict['vnf_dst_name'] == vnf_dst_name and
571 chain_dict['vnf_dst_interface'] == vnf_dst_interface)]
stevenvanrossembecc7c52016-11-07 05:52:01 +0100572
573 if len(found_chains) > 0:
574 # this chain exists, so need an extra monitoring flow
575 # assume only 1 chain per vnf/interface pair
576 LOG.debug('*** installing monitoring chain on top of pre-defined chain from {0}:{1} -> {2}:{3}'.
peusterm72f09882018-05-15 17:10:27 +0200577 format(vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface))
stevenvanrossembecc7c52016-11-07 05:52:01 +0100578 tag = found_chains[0]['tag']
579 ret = self._addMonitorFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface,
peusterm72f09882018-05-15 17:10:27 +0200580 tag=tag, table_id=0, **kwargs)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100581 return ret
582 else:
583 # no chain existing (or E-LAN) -> install normal chain
stevenvanrossem4fac2af2016-12-22 01:26:02 +0100584 LOG.warning('*** installing monitoring chain without pre-defined NSD chain from {0}:{1} -> {2}:{3}'.
stevenvanrossembecc7c52016-11-07 05:52:01 +0100585 format(vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface))
586 pass
587
splietker7b38ee12017-06-28 17:24:01 +0200588 cmd = kwargs.get('cmd', 'add-flow')
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100589 if cmd == 'add-flow' or cmd == 'del-flows':
peusterm72f09882018-05-15 17:10:27 +0200590 ret = self._chainAddFlow(
591 vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface, **kwargs)
stevenvanrossem461941c2016-05-10 11:41:29 +0200592 if kwargs.get('bidirectional'):
jokac304ad32017-01-09 10:58:23 +0100593 if kwargs.get('path') is not None:
594 kwargs['path'] = list(reversed(kwargs.get('path')))
peusterm72f09882018-05-15 17:10:27 +0200595 ret = ret + '\n' + \
596 self._chainAddFlow(
597 vnf_dst_name, vnf_src_name, vnf_dst_interface, vnf_src_interface, **kwargs)
stevenvanrossem9315da42016-04-11 12:10:06 +0200598
stevenvanrossem461941c2016-05-10 11:41:29 +0200599 else:
stevenvanrossem81955a52016-05-12 14:34:12 +0200600 ret = "Command unknown"
601
602 return ret
stevenvanrossem461941c2016-05-10 11:41:29 +0200603
peusterm72f09882018-05-15 17:10:27 +0200604 def _chainAddFlow(self, vnf_src_name, vnf_dst_name,
605 vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
stevenvanrossem461941c2016-05-10 11:41:29 +0200606
peusterm53d3c142016-07-18 10:10:11 +0200607 src_sw = None
peusterm53d3c142016-07-18 10:10:11 +0200608 src_sw_inport_nr = 0
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100609 src_sw_inport_name = None
610 dst_sw = None
peusterm53d3c142016-07-18 10:10:11 +0200611 dst_sw_outport_nr = 0
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100612 dst_sw_outport_name = None
peusterm53d3c142016-07-18 10:10:11 +0200613
614 LOG.debug("call chainAddFlow vnf_src_name=%r, vnf_src_interface=%r, vnf_dst_name=%r, vnf_dst_interface=%r",
615 vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface)
616
peusterm72f09882018-05-15 17:10:27 +0200617 # check if port is specified (vnf:port)
stevenvanrossemed711fd2016-04-11 16:59:29 +0200618 if vnf_src_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200619 # take first interface by default
620 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
621 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200622 vnf_src_interface = link_dict[0]['src_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200623
stevenvanrossem9315da42016-04-11 12:10:06 +0200624 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
625 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
626 for link in link_dict:
peusterm53d3c142016-07-18 10:10:11 +0200627 if (link_dict[link]['src_port_id'] == vnf_src_interface or
628 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 +0200629 # found the right link and connected switch
stevenvanrossem9315da42016-04-11 12:10:06 +0200630 src_sw = connected_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200631 src_sw_inport_nr = link_dict[link]['dst_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100632 src_sw_inport_name = link_dict[link]['dst_port_name']
stevenvanrossem9315da42016-04-11 12:10:06 +0200633 break
634
stevenvanrossemed711fd2016-04-11 16:59:29 +0200635 if vnf_dst_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200636 # take first interface by default
637 connected_sw = self.DCNetwork_graph.neighbors(vnf_dst_name)[0]
638 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200639 vnf_dst_interface = link_dict[0]['dst_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200640
641 vnf_dst_name = vnf_dst_name.split(':')[0]
642 for connected_sw in self.DCNetwork_graph.neighbors(vnf_dst_name):
643 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
644 for link in link_dict:
peusterm53d3c142016-07-18 10:10:11 +0200645 if link_dict[link]['dst_port_id'] == vnf_dst_interface or \
646 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 +0200647 # found the right link and connected switch
648 dst_sw = connected_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200649 dst_sw_outport_nr = link_dict[link]['src_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100650 dst_sw_outport_name = link_dict[link]['src_port_name']
stevenvanrossem9315da42016-04-11 12:10:06 +0200651 break
652
jokac304ad32017-01-09 10:58:23 +0100653 path = kwargs.get('path')
654 if path is None:
655 # get shortest path
656 try:
657 # returns the first found shortest path
658 # if all shortest paths are wanted, use: all_shortest_paths
peusterm72f09882018-05-15 17:10:27 +0200659 path = nx.shortest_path(
660 self.DCNetwork_graph, src_sw, dst_sw, weight=kwargs.get('weight'))
661 except BaseException:
jokac304ad32017-01-09 10:58:23 +0100662 LOG.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
663 vnf_src_name, vnf_dst_name, src_sw, dst_sw))
664 LOG.debug("Graph nodes: %r" % self.DCNetwork_graph.nodes())
665 LOG.debug("Graph edges: %r" % self.DCNetwork_graph.edges())
666 for e, v in self.DCNetwork_graph.edges():
667 LOG.debug("%r" % self.DCNetwork_graph[e][v])
peusterm72f09882018-05-15 17:10:27 +0200668 return "No path could be found between {0} and {1}".format(
669 vnf_src_name, vnf_dst_name)
stevenvanrossem9315da42016-04-11 12:10:06 +0200670
peusterm37911562018-10-18 15:03:55 +0200671 LOG.debug("Creating path between {0} and {1}: {2}".format(
peusterm72f09882018-05-15 17:10:27 +0200672 vnf_src_name, vnf_dst_name, path))
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100673
stevenvanrossem9315da42016-04-11 12:10:06 +0200674 current_hop = src_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200675 switch_inport_nr = src_sw_inport_nr
stevenvanrossem9315da42016-04-11 12:10:06 +0200676
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100677 # choose free vlan
stevenvanrossem27b6d952016-05-10 16:37:57 +0200678 cmd = kwargs.get('cmd')
679 vlan = None
stevenvanrossem263eee52017-02-08 01:04:36 +0100680 if cmd == 'add-flow':
stevenvanrossembecc7c52016-11-07 05:52:01 +0100681 if kwargs.get('tag'):
682 # use pre-defined tag
683 vlan = kwargs.get('tag')
684 else:
685 vlan = self.vlans.pop()
stevenvanrossem461941c2016-05-10 11:41:29 +0200686
stevenvanrossembecc7c52016-11-07 05:52:01 +0100687 # store the used vlan tag to identify this chain
688 if not kwargs.get('monitor'):
689 chain_dict = {}
690 chain_dict['vnf_src_name'] = vnf_src_name
691 chain_dict['vnf_dst_name'] = vnf_dst_name
692 chain_dict['vnf_src_interface'] = vnf_src_interface
693 chain_dict['vnf_dst_interface'] = vnf_dst_interface
694 chain_dict['tag'] = vlan
695 self.installed_chains.append(chain_dict)
696
peusterm72f09882018-05-15 17:10:27 +0200697 # iterate through the path to install the flow-entries
698 for i in range(0, len(path)):
stevenvanrossem9315da42016-04-11 12:10:06 +0200699 current_node = self.getNodeByName(current_hop)
stevenvanrossem461941c2016-05-10 11:41:29 +0200700
jokac304ad32017-01-09 10:58:23 +0100701 if i < len(path) - 1:
702 next_hop = path[i + 1]
stevenvanrossem9315da42016-04-11 12:10:06 +0200703 else:
jokac304ad32017-01-09 10:58:23 +0100704 # last switch reached
stevenvanrossem9315da42016-04-11 12:10:06 +0200705 next_hop = vnf_dst_name
706
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100707 next_node = self.getNodeByName(next_hop)
708
709 if next_hop == vnf_dst_name:
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200710 switch_outport_nr = dst_sw_outport_nr
peusterm37911562018-10-18 15:03:55 +0200711 LOG.debug("end node reached: {0}".format(vnf_dst_name))
peusterm72f09882018-05-15 17:10:27 +0200712 elif not isinstance(next_node, OVSSwitch):
peustermf9a817d2016-07-18 09:06:04 +0200713 LOG.info("Next node: {0} is not a switch".format(next_hop))
stevenvanrossemeefea6c2016-02-17 12:03:26 +0100714 return "Next node: {0} is not a switch".format(next_hop)
stevenvanrossem9315da42016-04-11 12:10:06 +0200715 else:
stevenvanrossemed711fd2016-04-11 16:59:29 +0200716 # take first link between switches by default
stevenvanrossem9315da42016-04-11 12:10:06 +0200717 index_edge_out = 0
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200718 switch_outport_nr = self.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
stevenvanrossem9315da42016-04-11 12:10:06 +0200719
peusterm72f09882018-05-15 17:10:27 +0200720 # set OpenFlow entry
721 if isinstance(current_node, OVSSwitch):
stevenvanrossem461941c2016-05-10 11:41:29 +0200722 kwargs['vlan'] = vlan
723 kwargs['path'] = path
724 kwargs['current_hop'] = current_hop
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100725 kwargs['switch_inport_name'] = src_sw_inport_name
726 kwargs['switch_outport_name'] = dst_sw_outport_name
jokac304ad32017-01-09 10:58:23 +0100727 kwargs['pathindex'] = i
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200728
729 if self.controller == RemoteController:
peusterm72f09882018-05-15 17:10:27 +0200730 # set flow entry via ryu rest api
731 self._set_flow_entry_ryu_rest(
732 current_node, switch_inport_nr, switch_outport_nr, **kwargs)
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200733 else:
peusterm72f09882018-05-15 17:10:27 +0200734 # set flow entry via ovs-ofctl
735 self._set_flow_entry_dpctl(
736 current_node, switch_inport_nr, switch_outport_nr, **kwargs)
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200737
stevenvanrossemed711fd2016-04-11 16:59:29 +0200738 # take first link between switches by default
peusterm72f09882018-05-15 17:10:27 +0200739 if isinstance(next_node, OVSSwitch):
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200740 switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
stevenvanrossemed711fd2016-04-11 16:59:29 +0200741 current_hop = next_hop
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100742
stevenvanrossemae588012017-06-06 10:33:19 +0200743 flow_options = {
peusterm72f09882018-05-15 17:10:27 +0200744 'priority': kwargs.get('priority', DEFAULT_PRIORITY),
745 'cookie': kwargs.get('cookie', DEFAULT_COOKIE),
746 'vlan': kwargs['vlan'],
747 'path': kwargs['path'],
748 'match_input': kwargs.get('match')
stevenvanrossemae588012017-06-06 10:33:19 +0200749 }
750 flow_options_str = json.dumps(flow_options, indent=1)
peusterm37911562018-10-18 15:03:55 +0200751 LOG.info("Installed flow rule: ({}:{}) -> ({}:{}) with options: {}"
752 .format(vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface, flow_options))
peusterm72f09882018-05-15 17:10:27 +0200753 return "success: {2} between {0} and {1} with options: {3}".format(
754 vnf_src_name, vnf_dst_name, cmd, flow_options_str)
stevenvanrossem27b6d952016-05-10 16:37:57 +0200755
peusterm72f09882018-05-15 17:10:27 +0200756 def _set_flow_entry_ryu_rest(
757 self, node, switch_inport_nr, switch_outport_nr, **kwargs):
stevenvanrossem27b6d952016-05-10 16:37:57 +0200758 match = 'in_port=%s' % switch_inport_nr
759
760 cookie = kwargs.get('cookie')
761 match_input = kwargs.get('match')
762 cmd = kwargs.get('cmd')
763 path = kwargs.get('path')
jokac304ad32017-01-09 10:58:23 +0100764 index = kwargs.get('pathindex')
schillinge83d22492019-02-20 11:04:08 +0100765 mod_dl_dst = kwargs.get('mod_dl_dst')
jokac304ad32017-01-09 10:58:23 +0100766
stevenvanrossem27b6d952016-05-10 16:37:57 +0200767 vlan = kwargs.get('vlan')
stevenvanrossemae588012017-06-06 10:33:19 +0200768 priority = kwargs.get('priority', DEFAULT_PRIORITY)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100769 # flag to not set the ovs port vlan tag
770 skip_vlan_tag = kwargs.get('skip_vlan_tag')
771 # table id to put this flowentry
772 table_id = kwargs.get('table_id')
773 if not table_id:
774 table_id = 0
stevenvanrossem27b6d952016-05-10 16:37:57 +0200775
776 s = ','
777 if match_input:
778 match = s.join([match, match_input])
779
780 flow = {}
781 flow['dpid'] = int(node.dpid, 16)
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200782
stevenvanrossem27b6d952016-05-10 16:37:57 +0200783 if cookie:
784 flow['cookie'] = int(cookie)
stevenvanrossem61699eb2016-08-05 15:57:59 +0200785 if priority:
786 flow['priority'] = int(priority)
stevenvanrossem27b6d952016-05-10 16:37:57 +0200787
stevenvanrossembecc7c52016-11-07 05:52:01 +0100788 flow['table_id'] = table_id
789
stevenvanrossem27b6d952016-05-10 16:37:57 +0200790 flow['actions'] = []
791
792 # possible Ryu actions, match fields:
793 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#add-a-flow-entry
794 if cmd == 'add-flow':
795 prefix = 'stats/flowentry/add'
peusterm72f09882018-05-15 17:10:27 +0200796 if vlan is not None:
jokac304ad32017-01-09 10:58:23 +0100797 if index == 0: # first node
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100798 # set vlan tag in ovs instance (to isolate E-LANs)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100799 if not skip_vlan_tag:
800 in_port_name = kwargs.get('switch_inport_name')
801 self._set_vlan_tag(node, in_port_name, vlan)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100802 # set vlan push action if more than 1 switch in the path
803 if len(path) > 1:
804 action = {}
peusterm72f09882018-05-15 17:10:27 +0200805 # Push a new VLAN tag if a input frame is
806 # non-VLAN-tagged
807 action['type'] = 'PUSH_VLAN'
808 # Ethertype 0x8100(=33024): IEEE 802.1Q VLAN-tagged
809 # frame
810 action['ethertype'] = 33024
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100811 flow['actions'].append(action)
812 action = {}
813 action['type'] = 'SET_FIELD'
814 action['field'] = 'vlan_vid'
stevenvanrossem9cc73602017-01-27 23:37:29 +0100815 # ryu expects the field to be masked
816 action['value'] = vlan | 0x1000
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100817 flow['actions'].append(action)
818
stevenvanrossem9cc73602017-01-27 23:37:29 +0100819 elif index == len(path) - 1: # last node
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100820 # set vlan tag in ovs instance (to isolate E-LANs)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100821 if not skip_vlan_tag:
822 out_port_name = kwargs.get('switch_outport_name')
823 self._set_vlan_tag(node, out_port_name, vlan)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100824 # set vlan pop action if more than 1 switch in the path
825 if len(path) > 1:
826 match += ',dl_vlan=%s' % vlan
827 action = {}
828 action['type'] = 'POP_VLAN'
829 flow['actions'].append(action)
830
stevenvanrossem27b6d952016-05-10 16:37:57 +0200831 else: # middle nodes
stevenvanrossem27b6d952016-05-10 16:37:57 +0200832 match += ',dl_vlan=%s' % vlan
schillinge83d22492019-02-20 11:04:08 +0100833 if mod_dl_dst:
834 action = {}
835 action['type'] = 'SET_FIELD'
836 action['field'] = 'eth_dst'
837 action['value'] = mod_dl_dst
838 flow['actions'].append(action)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100839
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200840 # output action must come last
841 action = {}
842 action['type'] = 'OUTPUT'
843 action['port'] = switch_outport_nr
844 flow['actions'].append(action)
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200845
stevenvanrossem27b6d952016-05-10 16:37:57 +0200846 elif cmd == 'del-flows':
stevenvanrossem27b6d952016-05-10 16:37:57 +0200847 prefix = 'stats/flowentry/delete'
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200848
stevenvanrossem27b6d952016-05-10 16:37:57 +0200849 if cookie:
stevenvanrossem1ef77022016-05-12 16:36:10 +0200850 # TODO: add cookie_mask as argument
peusterm72f09882018-05-15 17:10:27 +0200851 # need full mask to match complete cookie
852 flow['cookie_mask'] = int('0xffffffffffffffff', 16)
stevenvanrossem27b6d952016-05-10 16:37:57 +0200853
854 action = {}
855 action['type'] = 'OUTPUT'
856 action['port'] = switch_outport_nr
857 flow['actions'].append(action)
858
859 flow['match'] = self._parse_match(match)
860 self.ryu_REST(prefix, data=flow)
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100861
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100862 def _set_vlan_tag(self, node, switch_port, tag):
peusterm72f09882018-05-15 17:10:27 +0200863 node.vsctl('set', 'port {0} tag={1}'.format(switch_port, tag))
864 LOG.debug("set vlan in switch: {0} in_port: {1} vlan tag: {2}".format(
865 node.name, switch_port, tag))
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100866
peusterm72f09882018-05-15 17:10:27 +0200867 def _set_flow_entry_dpctl(
868 self, node, switch_inport_nr, switch_outport_nr, **kwargs):
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100869
stevenvanrossem23c48092016-05-06 17:21:12 +0200870 match = 'in_port=%s' % switch_inport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200871
872 cookie = kwargs.get('cookie')
873 match_input = kwargs.get('match')
874 cmd = kwargs.get('cmd')
875 path = kwargs.get('path')
jokac304ad32017-01-09 10:58:23 +0100876 index = kwargs.get('pathindex')
stevenvanrossem461941c2016-05-10 11:41:29 +0200877 vlan = kwargs.get('vlan')
878
stevenvanrossem898a2af2016-05-06 18:28:57 +0200879 s = ','
880 if cookie:
881 cookie = 'cookie=%s' % cookie
882 match = s.join([cookie, match])
stevenvanrossem23c48092016-05-06 17:21:12 +0200883 if match_input:
stevenvanrossem23c48092016-05-06 17:21:12 +0200884 match = s.join([match, match_input])
stevenvanrossem23c48092016-05-06 17:21:12 +0200885 if cmd == 'add-flow':
886 action = 'action=%s' % switch_outport_nr
peusterm72f09882018-05-15 17:10:27 +0200887 if vlan is not None:
888 if index == 0: # first node
889 action = ('action=mod_vlan_vid:%s' % vlan) + \
890 (',output=%s' % switch_outport_nr)
stevenvanrossem461941c2016-05-10 11:41:29 +0200891 match = '-O OpenFlow13 ' + match
jokac304ad32017-01-09 10:58:23 +0100892 elif index == len(path) - 1: # last node
stevenvanrossem461941c2016-05-10 11:41:29 +0200893 match += ',dl_vlan=%s' % vlan
894 action = 'action=strip_vlan,output=%s' % switch_outport_nr
895 else: # middle nodes
896 match += ',dl_vlan=%s' % vlan
stevenvanrossem23c48092016-05-06 17:21:12 +0200897 ofcmd = s.join([match, action])
898 elif cmd == 'del-flows':
899 ofcmd = match
900 else:
901 ofcmd = ''
902
903 node.dpctl(cmd, ofcmd)
peustermf9a817d2016-07-18 09:06:04 +0200904 LOG.info("{3} in switch: {0} in_port: {1} out_port: {2}".format(node.name, switch_inport_nr,
peusterm72f09882018-05-15 17:10:27 +0200905 switch_outport_nr, cmd))
stevenvanrossem23c48092016-05-06 17:21:12 +0200906
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100907 # start Ryu Openflow controller as Remote Controller for the DCNetwork
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200908 def startRyu(self, learning_switch=True):
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100909 # start Ryu controller with rest-API
schillinge06dc1252019-01-31 10:00:02 +0100910
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100911 # ryu default learning switch
schillinge06dc1252019-01-31 10:00:02 +0100912 # ryu_learning_app = python_install_path + '/ryu/app/simple_switch_13.py'
peusterm72f09882018-05-15 17:10:27 +0200913 # custom learning switch that installs a default NORMAL action in the
914 # ovs switches
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100915 dir_path = os.path.dirname(os.path.realpath(__file__))
schillinge06dc1252019-01-31 10:00:02 +0100916 ryu_learning_app = dir_path + '/son_emu_simple_switch_13.py'
917 ryu_rest_app = 'ryu.app.ofctl_rest'
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100918 # change the default Openflow controller port to 6653 (official IANA-assigned port number), as used by Mininet
919 # Ryu still uses 6633 as default
920 ryu_option = '--ofp-tcp-listen-port'
921 ryu_of_port = '6653'
peustermde14f332016-03-15 16:14:21 +0100922 ryu_cmd = 'ryu-manager'
peustermef6629e2016-03-14 17:21:56 +0100923 FNULL = open("/tmp/ryu.log", 'w')
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200924 if learning_switch:
schillinge06dc1252019-01-31 10:00:02 +0100925 # learning and rest api
926 args = [ryu_cmd, ryu_learning_app, ryu_rest_app, ryu_option, ryu_of_port]
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200927 else:
stevenvanrossem73efd192016-06-29 01:44:07 +0200928 # no learning switch, but with rest api
schillinge06dc1252019-01-31 10:00:02 +0100929 args = [ryu_cmd, ryu_rest_app, ryu_option, ryu_of_port]
930 self.ryu_process = Popen(args, stdout=FNULL, stderr=FNULL)
931 LOG.debug('starting ryu-controller with %s' % args)
peusterm391773a2016-03-14 17:40:43 +0100932 time.sleep(1)
933
peusterm8b04b532016-07-19 16:55:38 +0200934 def killRyu(self):
935 """
936 Stop the Ryu controller that might be started by son-emu.
937 :return:
938 """
939 # try it nicely
peustermde14f332016-03-15 16:14:21 +0100940 if self.ryu_process is not None:
peusterm391773a2016-03-14 17:40:43 +0100941 self.ryu_process.terminate()
942 self.ryu_process.kill()
peusterm8b04b532016-07-19 16:55:38 +0200943 # ensure its death ;-)
stevenvanrossem89706802016-07-19 02:54:45 +0200944 Popen(['pkill', '-f', 'ryu-manager'])
peusterm391773a2016-03-14 17:40:43 +0100945
stevenvanrossem27b6d952016-05-10 16:37:57 +0200946 def ryu_REST(self, prefix, dpid=None, data=None):
stevenvanrossem27b6d952016-05-10 16:37:57 +0200947
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200948 if dpid:
949 url = self.ryu_REST_api + '/' + str(prefix) + '/' + str(dpid)
950 else:
951 url = self.ryu_REST_api + '/' + str(prefix)
schillinge56eaae32019-02-20 13:09:20 +0100952
953 LOG.debug('sending RYU command: %s, payload: %s', url, data)
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200954 if data:
955 req = self.RyuSession.post(url, json=data)
956 else:
957 req = self.RyuSession.get(url)
958
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200959 # do extra logging if status code is not 200 (OK)
960 if req.status_code is not requests.codes.ok:
schillingec9b2e402019-02-20 11:14:22 +0100961 LOG.info(
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200962 'type {0} encoding: {1} text: {2} headers: {3} history: {4}'.format(req.headers['content-type'],
963 req.encoding, req.text,
964 req.headers, req.history))
965 LOG.info('url: {0}'.format(str(url)))
peusterm72f09882018-05-15 17:10:27 +0200966 if data:
967 LOG.info('POST: {0}'.format(str(data)))
968 LOG.info('status: {0} reason: {1}'.format(
969 req.status_code, req.reason))
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200970
971 if 'json' in req.headers['content-type']:
972 ret = req.json()
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200973 return ret
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200974
975 ret = req.text.rstrip()
976 return ret
977
stevenvanrossem27b6d952016-05-10 16:37:57 +0200978 # need to respect that some match fields must be integers
979 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#description-of-match-and-actions
peusterm72f09882018-05-15 17:10:27 +0200980
stevenvanrossem27b6d952016-05-10 16:37:57 +0200981 def _parse_match(self, match):
982 matches = match.split(',')
983 dict = {}
984 for m in matches:
985 match = m.split('=')
986 if len(match) == 2:
987 try:
988 m2 = int(match[1], 0)
peusterm72f09882018-05-15 17:10:27 +0200989 except BaseException:
stevenvanrossem27b6d952016-05-10 16:37:57 +0200990 m2 = match[1]
991
peusterm72f09882018-05-15 17:10:27 +0200992 dict.update({match[0]: m2})
stevenvanrossem27b6d952016-05-10 16:37:57 +0200993 return dict
994
peusterm72f09882018-05-15 17:10:27 +0200995 def find_connected_dc_interface(
996 self, vnf_src_name, vnf_src_interface=None):
stevenvanrossem17b6e882017-05-04 16:51:34 +0200997
998 if vnf_src_interface is None:
999 # take first interface by default
1000 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
1001 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
1002 vnf_src_interface = link_dict[0]['src_port_id']
1003
stevenvanrossem566779d2016-11-07 06:33:44 +01001004 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
1005 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
1006 for link in link_dict:
1007 if (link_dict[link]['src_port_id'] == vnf_src_interface or
peusterm72f09882018-05-15 17:10:27 +02001008 link_dict[link]['src_port_name'] == vnf_src_interface):
1009 # Fix: we might also get interface names, e.g, from a son-emu-cli call
stevenvanrossem566779d2016-11-07 06:33:44 +01001010 # found the right link and connected switch
stevenvanrossem566779d2016-11-07 06:33:44 +01001011 src_sw_inport_name = link_dict[link]['dst_port_name']
stevenvanrossem7062cee2016-12-22 10:31:38 +01001012 return src_sw_inport_name