blob: 8b2c445eea55f0edeaaf3656b87b437df7c12176 [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
peustermdad98842019-04-12 15:10:19 +020034# from 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
peustermdad98842019-04-12 15:10:19 +020045# monkey.patch_all()
peusterm9435e982019-03-19 11:10:38 +010046
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
peusterm5e0efe52019-07-02 13:01:36 +0200388 def getNodeByName(self, name):
389 """
390 Wraps Containernet's getNodeByName method to avoid
391 key not found exceptions.
392 """
393 try:
394 return super(DCNetwork, self).getNodeByName(name)
395 except BaseException as ex:
396 LOG.warning("Node not found: {}".format(name))
397 LOG.debug("Node not found: {}".format(ex))
398 return None
399
stevenvanrossembecc7c52016-11-07 05:52:01 +0100400 def _addMonitorFlow(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None,
peusterm72f09882018-05-15 17:10:27 +0200401 tag=None, **kwargs):
stevenvanrossembf1754e2016-11-17 10:20:52 +0100402 """
403 Add a monitoring flow entry that adds a special flowentry/counter at the begin or end of a chain.
404 So this monitoring flowrule exists on top of a previously defined chain rule and uses the same vlan tag/routing.
405 :param vnf_src_name:
406 :param vnf_dst_name:
407 :param vnf_src_interface:
408 :param vnf_dst_interface:
409 :param tag: vlan tag to be used for this chain (same tag as existing chain)
410 :param monitor_placement: 'tx' or 'rx' indicating to place the extra flowentry resp. at the beginning or end of the chain
411 :return:
412 """
stevenvanrossembecc7c52016-11-07 05:52:01 +0100413
414 src_sw = None
415 src_sw_inport_nr = 0
416 src_sw_inport_name = None
417 dst_sw = None
418 dst_sw_outport_nr = 0
419 dst_sw_outport_name = None
420
421 LOG.debug("call AddMonitorFlow vnf_src_name=%r, vnf_src_interface=%r, vnf_dst_name=%r, vnf_dst_interface=%r",
422 vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface)
423
peusterm72f09882018-05-15 17:10:27 +0200424 # check if port is specified (vnf:port)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100425 if vnf_src_interface is None:
426 # take first interface by default
427 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
428 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
429 vnf_src_interface = link_dict[0]['src_port_id']
430
431 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
432 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
433 for link in link_dict:
434 if (link_dict[link]['src_port_id'] == vnf_src_interface or
435 link_dict[link]['src_port_name'] == vnf_src_interface): # Fix: we might also get interface names, e.g, from a son-emu-cli call
436 # found the right link and connected switch
437 src_sw = connected_sw
438 src_sw_inport_nr = link_dict[link]['dst_port_nr']
439 src_sw_inport_name = link_dict[link]['dst_port_name']
440 break
441
442 if vnf_dst_interface is None:
443 # take first interface by default
444 connected_sw = self.DCNetwork_graph.neighbors(vnf_dst_name)[0]
445 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
446 vnf_dst_interface = link_dict[0]['dst_port_id']
447
448 vnf_dst_name = vnf_dst_name.split(':')[0]
449 for connected_sw in self.DCNetwork_graph.neighbors(vnf_dst_name):
450 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
451 for link in link_dict:
452 if link_dict[link]['dst_port_id'] == vnf_dst_interface or \
453 link_dict[link]['dst_port_name'] == vnf_dst_interface: # Fix: we might also get interface names, e.g, from a son-emu-cli call
454 # found the right link and connected switch
455 dst_sw = connected_sw
456 dst_sw_outport_nr = link_dict[link]['src_port_nr']
457 dst_sw_outport_name = link_dict[link]['src_port_name']
458 break
459
460 if not tag >= 0:
461 LOG.exception('tag not valid: {0}'.format(tag))
462
463 # get shortest path
464 try:
465 # returns the first found shortest path
466 # if all shortest paths are wanted, use: all_shortest_paths
peusterm72f09882018-05-15 17:10:27 +0200467 path = nx.shortest_path(
468 self.DCNetwork_graph, src_sw, dst_sw, weight=kwargs.get('weight'))
469 except BaseException:
stevenvanrossembecc7c52016-11-07 05:52:01 +0100470 LOG.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
471 vnf_src_name, vnf_dst_name, src_sw, dst_sw))
472 LOG.debug("Graph nodes: %r" % self.DCNetwork_graph.nodes())
473 LOG.debug("Graph edges: %r" % self.DCNetwork_graph.edges())
474 for e, v in self.DCNetwork_graph.edges():
475 LOG.debug("%r" % self.DCNetwork_graph[e][v])
peusterm72f09882018-05-15 17:10:27 +0200476 return "No path could be found between {0} and {1}".format(
477 vnf_src_name, vnf_dst_name)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100478
peusterm37911562018-10-18 15:03:55 +0200479 LOG.debug("Creating path between {0} and {1}: {2}".format(
peusterm72f09882018-05-15 17:10:27 +0200480 vnf_src_name, vnf_dst_name, path))
stevenvanrossembecc7c52016-11-07 05:52:01 +0100481
482 current_hop = src_sw
483 switch_inport_nr = src_sw_inport_nr
484
485 cmd = kwargs.get('cmd')
486
peusterm72f09882018-05-15 17:10:27 +0200487 # iterate through the path to install the flow-entries
488 for i in range(0, len(path)):
stevenvanrossembecc7c52016-11-07 05:52:01 +0100489 current_node = self.getNodeByName(current_hop)
490
peusterm72f09882018-05-15 17:10:27 +0200491 if path.index(current_hop) < len(path) - 1:
492 next_hop = path[path.index(current_hop) + 1]
stevenvanrossembecc7c52016-11-07 05:52:01 +0100493 else:
peusterm72f09882018-05-15 17:10:27 +0200494 # last switch reached
stevenvanrossembecc7c52016-11-07 05:52:01 +0100495 next_hop = vnf_dst_name
496
497 next_node = self.getNodeByName(next_hop)
498
499 if next_hop == vnf_dst_name:
500 switch_outport_nr = dst_sw_outport_nr
peusterm37911562018-10-18 15:03:55 +0200501 LOG.debug("end node reached: {0}".format(vnf_dst_name))
peusterm72f09882018-05-15 17:10:27 +0200502 elif not isinstance(next_node, OVSSwitch):
stevenvanrossembecc7c52016-11-07 05:52:01 +0100503 LOG.info("Next node: {0} is not a switch".format(next_hop))
504 return "Next node: {0} is not a switch".format(next_hop)
505 else:
506 # take first link between switches by default
507 index_edge_out = 0
508 switch_outport_nr = self.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100509
peusterm72f09882018-05-15 17:10:27 +0200510 # set of entry via ovs-ofctl
511 if isinstance(current_node, OVSSwitch):
stevenvanrossembecc7c52016-11-07 05:52:01 +0100512 kwargs['vlan'] = tag
513 kwargs['path'] = path
514 kwargs['current_hop'] = current_hop
515 kwargs['switch_inport_name'] = src_sw_inport_name
516 kwargs['switch_outport_name'] = dst_sw_outport_name
517 kwargs['skip_vlan_tag'] = True
stevenvanrossem263eee52017-02-08 01:04:36 +0100518 kwargs['pathindex'] = i
stevenvanrossembecc7c52016-11-07 05:52:01 +0100519
stevenvanrossem4fac2af2016-12-22 01:26:02 +0100520 monitor_placement = kwargs.get('monitor_placement').strip()
stevenvanrossembecc7c52016-11-07 05:52:01 +0100521 # put monitor flow at the dst switch
522 insert_flow = False
peusterm72f09882018-05-15 17:10:27 +0200523 # first node:
524 if monitor_placement == 'tx' and path.index(current_hop) == 0:
stevenvanrossembecc7c52016-11-07 05:52:01 +0100525 insert_flow = True
526 # put monitoring flow at the src switch
peusterm72f09882018-05-15 17:10:27 +0200527 # last node:
528 elif monitor_placement == 'rx' and path.index(current_hop) == len(path) - 1:
stevenvanrossembecc7c52016-11-07 05:52:01 +0100529 insert_flow = True
stevenvanrossem4fac2af2016-12-22 01:26:02 +0100530 elif monitor_placement not in ['rx', 'tx']:
peusterm72f09882018-05-15 17:10:27 +0200531 LOG.exception(
532 'invalid monitor command: {0}'.format(monitor_placement))
stevenvanrossembecc7c52016-11-07 05:52:01 +0100533
534 if self.controller == RemoteController and insert_flow:
peusterm72f09882018-05-15 17:10:27 +0200535 # set flow entry via ryu rest api
536 self._set_flow_entry_ryu_rest(
537 current_node, switch_inport_nr, switch_outport_nr, **kwargs)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100538 break
539 elif insert_flow:
peusterm72f09882018-05-15 17:10:27 +0200540 # set flow entry via ovs-ofctl
541 self._set_flow_entry_dpctl(
542 current_node, switch_inport_nr, switch_outport_nr, **kwargs)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100543 break
544
545 # take first link between switches by default
peusterm72f09882018-05-15 17:10:27 +0200546 if isinstance(next_node, OVSSwitch):
stevenvanrossembecc7c52016-11-07 05:52:01 +0100547 switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
548 current_hop = next_hop
549
peusterm72f09882018-05-15 17:10:27 +0200550 return "path {2} between {0} and {1}".format(
551 vnf_src_name, vnf_dst_name, cmd)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100552
peusterm72f09882018-05-15 17:10:27 +0200553 def setChain(self, vnf_src_name, vnf_dst_name,
554 vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200555 """
556 Chain 2 vnf interfaces together by installing the flowrules in the switches along their path.
557 Currently the path is found using the default networkx shortest path function.
558 Each chain gets a unique vlan id , so different chains wil not interfere.
559
560 :param vnf_src_name: vnf name (string)
561 :param vnf_dst_name: vnf name (string)
562 :param vnf_src_interface: source interface name (string)
563 :param vnf_dst_interface: destination interface name (string)
564 :param cmd: 'add-flow' (default) to add a chain, 'del-flows' to remove a chain
565 :param cookie: cookie for the installed flowrules (can be used later as identifier for a set of installed chains)
566 :param match: custom match entry to be added to the flowrules (default: only in_port and vlan tag)
567 :param priority: custom flowrule priority
stevenvanrossembf1754e2016-11-17 10:20:52 +0100568 :param monitor: boolean to indicate whether this chain is a monitoring chain
569 :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 +0100570 :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 +0100571 :param path: custom path between the two VNFs (list of switches)
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200572 :return: output log string
573 """
stevenvanrossembecc7c52016-11-07 05:52:01 +0100574
575 # special procedure for monitoring flows
576 if kwargs.get('monitor'):
577
578 # check if chain already exists
579 found_chains = [chain_dict for chain_dict in self.installed_chains if
peusterm72f09882018-05-15 17:10:27 +0200580 (chain_dict['vnf_src_name'] == vnf_src_name and
581 chain_dict['vnf_src_interface'] == vnf_src_interface and
582 chain_dict['vnf_dst_name'] == vnf_dst_name and
583 chain_dict['vnf_dst_interface'] == vnf_dst_interface)]
stevenvanrossembecc7c52016-11-07 05:52:01 +0100584
585 if len(found_chains) > 0:
586 # this chain exists, so need an extra monitoring flow
587 # assume only 1 chain per vnf/interface pair
588 LOG.debug('*** installing monitoring chain on top of pre-defined chain from {0}:{1} -> {2}:{3}'.
peusterm72f09882018-05-15 17:10:27 +0200589 format(vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface))
stevenvanrossembecc7c52016-11-07 05:52:01 +0100590 tag = found_chains[0]['tag']
591 ret = self._addMonitorFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface,
peusterm72f09882018-05-15 17:10:27 +0200592 tag=tag, table_id=0, **kwargs)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100593 return ret
594 else:
595 # no chain existing (or E-LAN) -> install normal chain
stevenvanrossem4fac2af2016-12-22 01:26:02 +0100596 LOG.warning('*** installing monitoring chain without pre-defined NSD chain from {0}:{1} -> {2}:{3}'.
stevenvanrossembecc7c52016-11-07 05:52:01 +0100597 format(vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface))
598 pass
599
splietker7b38ee12017-06-28 17:24:01 +0200600 cmd = kwargs.get('cmd', 'add-flow')
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100601 if cmd == 'add-flow' or cmd == 'del-flows':
peusterm72f09882018-05-15 17:10:27 +0200602 ret = self._chainAddFlow(
603 vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface, **kwargs)
stevenvanrossem461941c2016-05-10 11:41:29 +0200604 if kwargs.get('bidirectional'):
jokac304ad32017-01-09 10:58:23 +0100605 if kwargs.get('path') is not None:
606 kwargs['path'] = list(reversed(kwargs.get('path')))
peusterm72f09882018-05-15 17:10:27 +0200607 ret = ret + '\n' + \
608 self._chainAddFlow(
609 vnf_dst_name, vnf_src_name, vnf_dst_interface, vnf_src_interface, **kwargs)
stevenvanrossem9315da42016-04-11 12:10:06 +0200610
stevenvanrossem461941c2016-05-10 11:41:29 +0200611 else:
stevenvanrossem81955a52016-05-12 14:34:12 +0200612 ret = "Command unknown"
613
614 return ret
stevenvanrossem461941c2016-05-10 11:41:29 +0200615
peusterm72f09882018-05-15 17:10:27 +0200616 def _chainAddFlow(self, vnf_src_name, vnf_dst_name,
617 vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
stevenvanrossem461941c2016-05-10 11:41:29 +0200618
peusterm53d3c142016-07-18 10:10:11 +0200619 src_sw = None
peusterm53d3c142016-07-18 10:10:11 +0200620 src_sw_inport_nr = 0
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100621 src_sw_inport_name = None
622 dst_sw = None
peusterm53d3c142016-07-18 10:10:11 +0200623 dst_sw_outport_nr = 0
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100624 dst_sw_outport_name = None
peusterm53d3c142016-07-18 10:10:11 +0200625
626 LOG.debug("call chainAddFlow vnf_src_name=%r, vnf_src_interface=%r, vnf_dst_name=%r, vnf_dst_interface=%r",
627 vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface)
628
peusterm72f09882018-05-15 17:10:27 +0200629 # check if port is specified (vnf:port)
stevenvanrossemed711fd2016-04-11 16:59:29 +0200630 if vnf_src_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200631 # take first interface by default
632 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
633 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200634 vnf_src_interface = link_dict[0]['src_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200635
stevenvanrossem9315da42016-04-11 12:10:06 +0200636 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
637 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
638 for link in link_dict:
peusterm53d3c142016-07-18 10:10:11 +0200639 if (link_dict[link]['src_port_id'] == vnf_src_interface or
640 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 +0200641 # found the right link and connected switch
stevenvanrossem9315da42016-04-11 12:10:06 +0200642 src_sw = connected_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200643 src_sw_inport_nr = link_dict[link]['dst_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100644 src_sw_inport_name = link_dict[link]['dst_port_name']
stevenvanrossem9315da42016-04-11 12:10:06 +0200645 break
646
stevenvanrossemed711fd2016-04-11 16:59:29 +0200647 if vnf_dst_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200648 # take first interface by default
649 connected_sw = self.DCNetwork_graph.neighbors(vnf_dst_name)[0]
650 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200651 vnf_dst_interface = link_dict[0]['dst_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200652
653 vnf_dst_name = vnf_dst_name.split(':')[0]
654 for connected_sw in self.DCNetwork_graph.neighbors(vnf_dst_name):
655 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
656 for link in link_dict:
peusterm53d3c142016-07-18 10:10:11 +0200657 if link_dict[link]['dst_port_id'] == vnf_dst_interface or \
658 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 +0200659 # found the right link and connected switch
660 dst_sw = connected_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200661 dst_sw_outport_nr = link_dict[link]['src_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100662 dst_sw_outport_name = link_dict[link]['src_port_name']
stevenvanrossem9315da42016-04-11 12:10:06 +0200663 break
664
jokac304ad32017-01-09 10:58:23 +0100665 path = kwargs.get('path')
666 if path is None:
667 # get shortest path
668 try:
669 # returns the first found shortest path
670 # if all shortest paths are wanted, use: all_shortest_paths
peusterm72f09882018-05-15 17:10:27 +0200671 path = nx.shortest_path(
672 self.DCNetwork_graph, src_sw, dst_sw, weight=kwargs.get('weight'))
673 except BaseException:
jokac304ad32017-01-09 10:58:23 +0100674 LOG.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
675 vnf_src_name, vnf_dst_name, src_sw, dst_sw))
676 LOG.debug("Graph nodes: %r" % self.DCNetwork_graph.nodes())
677 LOG.debug("Graph edges: %r" % self.DCNetwork_graph.edges())
678 for e, v in self.DCNetwork_graph.edges():
679 LOG.debug("%r" % self.DCNetwork_graph[e][v])
peusterm72f09882018-05-15 17:10:27 +0200680 return "No path could be found between {0} and {1}".format(
681 vnf_src_name, vnf_dst_name)
stevenvanrossem9315da42016-04-11 12:10:06 +0200682
peusterm37911562018-10-18 15:03:55 +0200683 LOG.debug("Creating path between {0} and {1}: {2}".format(
peusterm72f09882018-05-15 17:10:27 +0200684 vnf_src_name, vnf_dst_name, path))
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100685
stevenvanrossem9315da42016-04-11 12:10:06 +0200686 current_hop = src_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200687 switch_inport_nr = src_sw_inport_nr
stevenvanrossem9315da42016-04-11 12:10:06 +0200688
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100689 # choose free vlan
stevenvanrossem27b6d952016-05-10 16:37:57 +0200690 cmd = kwargs.get('cmd')
691 vlan = None
stevenvanrossem263eee52017-02-08 01:04:36 +0100692 if cmd == 'add-flow':
stevenvanrossembecc7c52016-11-07 05:52:01 +0100693 if kwargs.get('tag'):
694 # use pre-defined tag
695 vlan = kwargs.get('tag')
696 else:
697 vlan = self.vlans.pop()
stevenvanrossem461941c2016-05-10 11:41:29 +0200698
stevenvanrossembecc7c52016-11-07 05:52:01 +0100699 # store the used vlan tag to identify this chain
700 if not kwargs.get('monitor'):
701 chain_dict = {}
702 chain_dict['vnf_src_name'] = vnf_src_name
703 chain_dict['vnf_dst_name'] = vnf_dst_name
704 chain_dict['vnf_src_interface'] = vnf_src_interface
705 chain_dict['vnf_dst_interface'] = vnf_dst_interface
706 chain_dict['tag'] = vlan
707 self.installed_chains.append(chain_dict)
708
peusterm72f09882018-05-15 17:10:27 +0200709 # iterate through the path to install the flow-entries
710 for i in range(0, len(path)):
stevenvanrossem9315da42016-04-11 12:10:06 +0200711 current_node = self.getNodeByName(current_hop)
stevenvanrossem461941c2016-05-10 11:41:29 +0200712
jokac304ad32017-01-09 10:58:23 +0100713 if i < len(path) - 1:
714 next_hop = path[i + 1]
stevenvanrossem9315da42016-04-11 12:10:06 +0200715 else:
jokac304ad32017-01-09 10:58:23 +0100716 # last switch reached
stevenvanrossem9315da42016-04-11 12:10:06 +0200717 next_hop = vnf_dst_name
718
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100719 next_node = self.getNodeByName(next_hop)
720
721 if next_hop == vnf_dst_name:
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200722 switch_outport_nr = dst_sw_outport_nr
peusterm37911562018-10-18 15:03:55 +0200723 LOG.debug("end node reached: {0}".format(vnf_dst_name))
peusterm72f09882018-05-15 17:10:27 +0200724 elif not isinstance(next_node, OVSSwitch):
peustermf9a817d2016-07-18 09:06:04 +0200725 LOG.info("Next node: {0} is not a switch".format(next_hop))
stevenvanrossemeefea6c2016-02-17 12:03:26 +0100726 return "Next node: {0} is not a switch".format(next_hop)
stevenvanrossem9315da42016-04-11 12:10:06 +0200727 else:
stevenvanrossemed711fd2016-04-11 16:59:29 +0200728 # take first link between switches by default
stevenvanrossem9315da42016-04-11 12:10:06 +0200729 index_edge_out = 0
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200730 switch_outport_nr = self.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
stevenvanrossem9315da42016-04-11 12:10:06 +0200731
peusterm72f09882018-05-15 17:10:27 +0200732 # set OpenFlow entry
733 if isinstance(current_node, OVSSwitch):
stevenvanrossem461941c2016-05-10 11:41:29 +0200734 kwargs['vlan'] = vlan
735 kwargs['path'] = path
736 kwargs['current_hop'] = current_hop
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100737 kwargs['switch_inport_name'] = src_sw_inport_name
738 kwargs['switch_outport_name'] = dst_sw_outport_name
jokac304ad32017-01-09 10:58:23 +0100739 kwargs['pathindex'] = i
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200740
741 if self.controller == RemoteController:
peusterm72f09882018-05-15 17:10:27 +0200742 # set flow entry via ryu rest api
743 self._set_flow_entry_ryu_rest(
744 current_node, switch_inport_nr, switch_outport_nr, **kwargs)
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200745 else:
peusterm72f09882018-05-15 17:10:27 +0200746 # set flow entry via ovs-ofctl
747 self._set_flow_entry_dpctl(
748 current_node, switch_inport_nr, switch_outport_nr, **kwargs)
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200749
stevenvanrossemed711fd2016-04-11 16:59:29 +0200750 # take first link between switches by default
peusterm72f09882018-05-15 17:10:27 +0200751 if isinstance(next_node, OVSSwitch):
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200752 switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
stevenvanrossemed711fd2016-04-11 16:59:29 +0200753 current_hop = next_hop
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100754
stevenvanrossemae588012017-06-06 10:33:19 +0200755 flow_options = {
peusterm72f09882018-05-15 17:10:27 +0200756 'priority': kwargs.get('priority', DEFAULT_PRIORITY),
757 'cookie': kwargs.get('cookie', DEFAULT_COOKIE),
758 'vlan': kwargs['vlan'],
759 'path': kwargs['path'],
760 'match_input': kwargs.get('match')
stevenvanrossemae588012017-06-06 10:33:19 +0200761 }
762 flow_options_str = json.dumps(flow_options, indent=1)
peusterm37911562018-10-18 15:03:55 +0200763 LOG.info("Installed flow rule: ({}:{}) -> ({}:{}) with options: {}"
764 .format(vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface, flow_options))
peusterm72f09882018-05-15 17:10:27 +0200765 return "success: {2} between {0} and {1} with options: {3}".format(
766 vnf_src_name, vnf_dst_name, cmd, flow_options_str)
stevenvanrossem27b6d952016-05-10 16:37:57 +0200767
peusterm72f09882018-05-15 17:10:27 +0200768 def _set_flow_entry_ryu_rest(
769 self, node, switch_inport_nr, switch_outport_nr, **kwargs):
stevenvanrossem27b6d952016-05-10 16:37:57 +0200770 match = 'in_port=%s' % switch_inport_nr
771
772 cookie = kwargs.get('cookie')
773 match_input = kwargs.get('match')
774 cmd = kwargs.get('cmd')
775 path = kwargs.get('path')
jokac304ad32017-01-09 10:58:23 +0100776 index = kwargs.get('pathindex')
schillinge83d22492019-02-20 11:04:08 +0100777 mod_dl_dst = kwargs.get('mod_dl_dst')
jokac304ad32017-01-09 10:58:23 +0100778
stevenvanrossem27b6d952016-05-10 16:37:57 +0200779 vlan = kwargs.get('vlan')
stevenvanrossemae588012017-06-06 10:33:19 +0200780 priority = kwargs.get('priority', DEFAULT_PRIORITY)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100781 # flag to not set the ovs port vlan tag
782 skip_vlan_tag = kwargs.get('skip_vlan_tag')
783 # table id to put this flowentry
784 table_id = kwargs.get('table_id')
785 if not table_id:
786 table_id = 0
stevenvanrossem27b6d952016-05-10 16:37:57 +0200787
788 s = ','
789 if match_input:
790 match = s.join([match, match_input])
791
792 flow = {}
793 flow['dpid'] = int(node.dpid, 16)
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200794
stevenvanrossem27b6d952016-05-10 16:37:57 +0200795 if cookie:
796 flow['cookie'] = int(cookie)
stevenvanrossem61699eb2016-08-05 15:57:59 +0200797 if priority:
798 flow['priority'] = int(priority)
stevenvanrossem27b6d952016-05-10 16:37:57 +0200799
stevenvanrossembecc7c52016-11-07 05:52:01 +0100800 flow['table_id'] = table_id
801
stevenvanrossem27b6d952016-05-10 16:37:57 +0200802 flow['actions'] = []
803
804 # possible Ryu actions, match fields:
805 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#add-a-flow-entry
806 if cmd == 'add-flow':
807 prefix = 'stats/flowentry/add'
peusterm72f09882018-05-15 17:10:27 +0200808 if vlan is not None:
jokac304ad32017-01-09 10:58:23 +0100809 if index == 0: # first node
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100810 # set vlan tag in ovs instance (to isolate E-LANs)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100811 if not skip_vlan_tag:
812 in_port_name = kwargs.get('switch_inport_name')
813 self._set_vlan_tag(node, in_port_name, vlan)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100814 # set vlan push action if more than 1 switch in the path
815 if len(path) > 1:
816 action = {}
peusterm72f09882018-05-15 17:10:27 +0200817 # Push a new VLAN tag if a input frame is
818 # non-VLAN-tagged
819 action['type'] = 'PUSH_VLAN'
820 # Ethertype 0x8100(=33024): IEEE 802.1Q VLAN-tagged
821 # frame
822 action['ethertype'] = 33024
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100823 flow['actions'].append(action)
824 action = {}
825 action['type'] = 'SET_FIELD'
826 action['field'] = 'vlan_vid'
stevenvanrossem9cc73602017-01-27 23:37:29 +0100827 # ryu expects the field to be masked
828 action['value'] = vlan | 0x1000
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100829 flow['actions'].append(action)
830
stevenvanrossem9cc73602017-01-27 23:37:29 +0100831 elif index == len(path) - 1: # last node
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100832 # set vlan tag in ovs instance (to isolate E-LANs)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100833 if not skip_vlan_tag:
834 out_port_name = kwargs.get('switch_outport_name')
835 self._set_vlan_tag(node, out_port_name, vlan)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100836 # set vlan pop action if more than 1 switch in the path
837 if len(path) > 1:
838 match += ',dl_vlan=%s' % vlan
839 action = {}
840 action['type'] = 'POP_VLAN'
841 flow['actions'].append(action)
842
stevenvanrossem27b6d952016-05-10 16:37:57 +0200843 else: # middle nodes
stevenvanrossem27b6d952016-05-10 16:37:57 +0200844 match += ',dl_vlan=%s' % vlan
schillinge83d22492019-02-20 11:04:08 +0100845 if mod_dl_dst:
846 action = {}
847 action['type'] = 'SET_FIELD'
848 action['field'] = 'eth_dst'
849 action['value'] = mod_dl_dst
850 flow['actions'].append(action)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100851
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200852 # output action must come last
853 action = {}
854 action['type'] = 'OUTPUT'
855 action['port'] = switch_outport_nr
856 flow['actions'].append(action)
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200857
stevenvanrossem27b6d952016-05-10 16:37:57 +0200858 elif cmd == 'del-flows':
stevenvanrossem27b6d952016-05-10 16:37:57 +0200859 prefix = 'stats/flowentry/delete'
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200860
stevenvanrossem27b6d952016-05-10 16:37:57 +0200861 if cookie:
stevenvanrossem1ef77022016-05-12 16:36:10 +0200862 # TODO: add cookie_mask as argument
peusterm72f09882018-05-15 17:10:27 +0200863 # need full mask to match complete cookie
864 flow['cookie_mask'] = int('0xffffffffffffffff', 16)
stevenvanrossem27b6d952016-05-10 16:37:57 +0200865
866 action = {}
867 action['type'] = 'OUTPUT'
868 action['port'] = switch_outport_nr
869 flow['actions'].append(action)
870
871 flow['match'] = self._parse_match(match)
872 self.ryu_REST(prefix, data=flow)
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100873
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100874 def _set_vlan_tag(self, node, switch_port, tag):
peusterm72f09882018-05-15 17:10:27 +0200875 node.vsctl('set', 'port {0} tag={1}'.format(switch_port, tag))
876 LOG.debug("set vlan in switch: {0} in_port: {1} vlan tag: {2}".format(
877 node.name, switch_port, tag))
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100878
peusterm72f09882018-05-15 17:10:27 +0200879 def _set_flow_entry_dpctl(
880 self, node, switch_inport_nr, switch_outport_nr, **kwargs):
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100881
stevenvanrossem23c48092016-05-06 17:21:12 +0200882 match = 'in_port=%s' % switch_inport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200883
884 cookie = kwargs.get('cookie')
885 match_input = kwargs.get('match')
886 cmd = kwargs.get('cmd')
887 path = kwargs.get('path')
jokac304ad32017-01-09 10:58:23 +0100888 index = kwargs.get('pathindex')
stevenvanrossem461941c2016-05-10 11:41:29 +0200889 vlan = kwargs.get('vlan')
890
stevenvanrossem898a2af2016-05-06 18:28:57 +0200891 s = ','
892 if cookie:
893 cookie = 'cookie=%s' % cookie
894 match = s.join([cookie, match])
stevenvanrossem23c48092016-05-06 17:21:12 +0200895 if match_input:
stevenvanrossem23c48092016-05-06 17:21:12 +0200896 match = s.join([match, match_input])
stevenvanrossem23c48092016-05-06 17:21:12 +0200897 if cmd == 'add-flow':
898 action = 'action=%s' % switch_outport_nr
peusterm72f09882018-05-15 17:10:27 +0200899 if vlan is not None:
900 if index == 0: # first node
901 action = ('action=mod_vlan_vid:%s' % vlan) + \
902 (',output=%s' % switch_outport_nr)
stevenvanrossem461941c2016-05-10 11:41:29 +0200903 match = '-O OpenFlow13 ' + match
jokac304ad32017-01-09 10:58:23 +0100904 elif index == len(path) - 1: # last node
stevenvanrossem461941c2016-05-10 11:41:29 +0200905 match += ',dl_vlan=%s' % vlan
906 action = 'action=strip_vlan,output=%s' % switch_outport_nr
907 else: # middle nodes
908 match += ',dl_vlan=%s' % vlan
stevenvanrossem23c48092016-05-06 17:21:12 +0200909 ofcmd = s.join([match, action])
910 elif cmd == 'del-flows':
911 ofcmd = match
912 else:
913 ofcmd = ''
914
915 node.dpctl(cmd, ofcmd)
peustermf9a817d2016-07-18 09:06:04 +0200916 LOG.info("{3} in switch: {0} in_port: {1} out_port: {2}".format(node.name, switch_inport_nr,
peusterm72f09882018-05-15 17:10:27 +0200917 switch_outport_nr, cmd))
stevenvanrossem23c48092016-05-06 17:21:12 +0200918
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100919 # start Ryu Openflow controller as Remote Controller for the DCNetwork
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200920 def startRyu(self, learning_switch=True):
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100921 # start Ryu controller with rest-API
schillinge06dc1252019-01-31 10:00:02 +0100922
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100923 # ryu default learning switch
schillinge06dc1252019-01-31 10:00:02 +0100924 # ryu_learning_app = python_install_path + '/ryu/app/simple_switch_13.py'
peusterm72f09882018-05-15 17:10:27 +0200925 # custom learning switch that installs a default NORMAL action in the
926 # ovs switches
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100927 dir_path = os.path.dirname(os.path.realpath(__file__))
schillinge06dc1252019-01-31 10:00:02 +0100928 ryu_learning_app = dir_path + '/son_emu_simple_switch_13.py'
929 ryu_rest_app = 'ryu.app.ofctl_rest'
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100930 # change the default Openflow controller port to 6653 (official IANA-assigned port number), as used by Mininet
931 # Ryu still uses 6633 as default
932 ryu_option = '--ofp-tcp-listen-port'
933 ryu_of_port = '6653'
peustermde14f332016-03-15 16:14:21 +0100934 ryu_cmd = 'ryu-manager'
peustermef6629e2016-03-14 17:21:56 +0100935 FNULL = open("/tmp/ryu.log", 'w')
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200936 if learning_switch:
schillinge06dc1252019-01-31 10:00:02 +0100937 # learning and rest api
938 args = [ryu_cmd, ryu_learning_app, ryu_rest_app, ryu_option, ryu_of_port]
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200939 else:
stevenvanrossem73efd192016-06-29 01:44:07 +0200940 # no learning switch, but with rest api
schillinge06dc1252019-01-31 10:00:02 +0100941 args = [ryu_cmd, ryu_rest_app, ryu_option, ryu_of_port]
942 self.ryu_process = Popen(args, stdout=FNULL, stderr=FNULL)
943 LOG.debug('starting ryu-controller with %s' % args)
peusterm391773a2016-03-14 17:40:43 +0100944 time.sleep(1)
945
peusterm8b04b532016-07-19 16:55:38 +0200946 def killRyu(self):
947 """
948 Stop the Ryu controller that might be started by son-emu.
949 :return:
950 """
951 # try it nicely
peustermcda16f92019-03-25 16:15:26 +0100952 try:
953 if self.ryu_process is not None:
954 self.ryu_process.terminate()
955 self.ryu_process.kill()
956 except BaseException as ex:
957 LOG.warning("Error during Ryu stop: {}".format(ex))
peusterm8b04b532016-07-19 16:55:38 +0200958 # ensure its death ;-)
stevenvanrossem89706802016-07-19 02:54:45 +0200959 Popen(['pkill', '-f', 'ryu-manager'])
peusterm391773a2016-03-14 17:40:43 +0100960
stevenvanrossem27b6d952016-05-10 16:37:57 +0200961 def ryu_REST(self, prefix, dpid=None, data=None):
stevenvanrossem27b6d952016-05-10 16:37:57 +0200962
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200963 if dpid:
964 url = self.ryu_REST_api + '/' + str(prefix) + '/' + str(dpid)
965 else:
966 url = self.ryu_REST_api + '/' + str(prefix)
schillinge56eaae32019-02-20 13:09:20 +0100967
968 LOG.debug('sending RYU command: %s, payload: %s', url, data)
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200969 if data:
970 req = self.RyuSession.post(url, json=data)
971 else:
972 req = self.RyuSession.get(url)
973
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200974 # do extra logging if status code is not 200 (OK)
975 if req.status_code is not requests.codes.ok:
schillingec9b2e402019-02-20 11:14:22 +0100976 LOG.info(
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200977 'type {0} encoding: {1} text: {2} headers: {3} history: {4}'.format(req.headers['content-type'],
978 req.encoding, req.text,
979 req.headers, req.history))
980 LOG.info('url: {0}'.format(str(url)))
peusterm72f09882018-05-15 17:10:27 +0200981 if data:
982 LOG.info('POST: {0}'.format(str(data)))
983 LOG.info('status: {0} reason: {1}'.format(
984 req.status_code, req.reason))
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200985
986 if 'json' in req.headers['content-type']:
987 ret = req.json()
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200988 return ret
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200989
990 ret = req.text.rstrip()
991 return ret
992
stevenvanrossem27b6d952016-05-10 16:37:57 +0200993 # need to respect that some match fields must be integers
994 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#description-of-match-and-actions
peusterm72f09882018-05-15 17:10:27 +0200995
stevenvanrossem27b6d952016-05-10 16:37:57 +0200996 def _parse_match(self, match):
997 matches = match.split(',')
998 dict = {}
999 for m in matches:
1000 match = m.split('=')
1001 if len(match) == 2:
1002 try:
1003 m2 = int(match[1], 0)
peusterm72f09882018-05-15 17:10:27 +02001004 except BaseException:
stevenvanrossem27b6d952016-05-10 16:37:57 +02001005 m2 = match[1]
1006
peusterm72f09882018-05-15 17:10:27 +02001007 dict.update({match[0]: m2})
stevenvanrossem27b6d952016-05-10 16:37:57 +02001008 return dict
1009
peusterm72f09882018-05-15 17:10:27 +02001010 def find_connected_dc_interface(
1011 self, vnf_src_name, vnf_src_interface=None):
stevenvanrossem17b6e882017-05-04 16:51:34 +02001012
1013 if vnf_src_interface is None:
1014 # take first interface by default
1015 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
1016 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
1017 vnf_src_interface = link_dict[0]['src_port_id']
1018
stevenvanrossem566779d2016-11-07 06:33:44 +01001019 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
1020 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
1021 for link in link_dict:
1022 if (link_dict[link]['src_port_id'] == vnf_src_interface or
peusterm72f09882018-05-15 17:10:27 +02001023 link_dict[link]['src_port_name'] == vnf_src_interface):
1024 # Fix: we might also get interface names, e.g, from a son-emu-cli call
stevenvanrossem566779d2016-11-07 06:33:44 +01001025 # found the right link and connected switch
stevenvanrossem566779d2016-11-07 06:33:44 +01001026 src_sw_inport_name = link_dict[link]['dst_port_name']
stevenvanrossem7062cee2016-12-22 10:31:38 +01001027 return src_sw_inport_name