blob: 340f340db957024edad9fc4b09a73d22b60b8765 [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
27
peustermef6629e2016-03-14 17:21:56 +010028import time
stevenvanrossem9ebd0942016-02-22 10:13:05 +010029from subprocess import Popen
stevenvanrossem6b1d9b92016-05-02 13:10:40 +020030import re
stevenvanrossem3fc13932016-08-09 23:39:16 +020031import requests
stevenvanrossemc3a344f2016-11-04 19:34:47 +010032import os
stevenvanrossemae588012017-06-06 10:33:19 +020033import json
stevenvanrossem9ebd0942016-02-22 10:13:05 +010034
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
stevenvanrossemc5a536a2016-02-16 14:52:39 +010040import networkx as nx
cgeoffroy9524ad32016-03-03 18:24:15 +010041from emuvim.dcemulator.monitoring import DCNetworkMonitor
peusterm72f09882018-05-15 17:10:27 +020042from emuvim.dcemulator.node import Datacenter, EmulatorCompute
peusterm42f08be2016-03-10 21:56:34 +010043from emuvim.dcemulator.resourcemodel import ResourceModelRegistrar
peustermcbcd4c22015-12-28 11:33:42 +010044
peustermf9a817d2016-07-18 09:06:04 +020045LOG = logging.getLogger("dcemulator.net")
46LOG.setLevel(logging.DEBUG)
47
stevenvanrossemb3f34172016-11-16 23:30:57 +010048# default CPU period used for cpu percentage-based cfs values (microseconds)
49CPU_PERIOD = 1000000
50
stevenvanrossemae588012017-06-06 10:33:19 +020051# default priority setting for added flow-rules
52DEFAULT_PRIORITY = 1000
53# default cookie number for new flow-rules
54DEFAULT_COOKIE = 10
55
peusterm72f09882018-05-15 17:10:27 +020056
peusterm5877ea22016-05-11 13:44:59 +020057class DCNetwork(Containernet):
peusterme4e89d32016-01-07 09:14:54 +010058 """
peusterm5877ea22016-05-11 13:44:59 +020059 Wraps the original Mininet/Containernet class and provides
peusterme4e89d32016-01-07 09:14:54 +010060 methods to add data centers, switches, etc.
61
62 This class is used by topology definition scripts.
63 """
peustermcbcd4c22015-12-28 11:33:42 +010064
peusterm0ec25102016-04-16 02:16:20 +020065 def __init__(self, controller=RemoteController, monitor=False,
peusterm72f09882018-05-15 17:10:27 +020066 enable_learning=False,
67 # learning switch behavior of the default ovs switches icw Ryu
68 # controller can be turned off/on, needed for E-LAN
69 # functionality
peusterma4d84792016-03-25 12:27:07 +010070 dc_emulation_max_cpu=1.0, # fraction of overall CPU time for emulation
71 dc_emulation_max_mem=512, # emulation max mem in MB
72 **kwargs):
peusterm42f08be2016-03-10 21:56:34 +010073 """
peusterm5877ea22016-05-11 13:44:59 +020074 Create an extended version of a Containernet network
peusterm42f08be2016-03-10 21:56:34 +010075 :param dc_emulation_max_cpu: max. CPU time used by containers in data centers
76 :param kwargs: path through for Mininet parameters
77 :return:
78 """
peusterm8b04b532016-07-19 16:55:38 +020079 # members
peustermcbcd4c22015-12-28 11:33:42 +010080 self.dcs = {}
peusterm8b04b532016-07-19 16:55:38 +020081 self.ryu_process = None
peusterm72f09882018-05-15 17:10:27 +020082 # list of deployed nsds.E_Lines and E_LANs (uploaded from the dummy
83 # gatekeeper)
stevenvanrossembecc7c52016-11-07 05:52:01 +010084 self.deployed_nsds = []
85 self.deployed_elines = []
86 self.deployed_elans = []
87 self.installed_chains = []
88
peusterm8b04b532016-07-19 16:55:38 +020089 # always cleanup environment before we start the emulator
stevenvanrossem89706802016-07-19 02:54:45 +020090 self.killRyu()
peusterm8b04b532016-07-19 16:55:38 +020091 cleanup()
stevenvanrossem89706802016-07-19 02:54:45 +020092
peusterm293cbc32016-01-13 17:05:28 +010093 # call original Docker.__init__ and setup default controller
peusterm5877ea22016-05-11 13:44:59 +020094 Containernet.__init__(
stevenvanrossem7cd3c252016-05-11 22:55:15 +020095 self, switch=OVSKernelSwitch, controller=controller, **kwargs)
stevenvanrossemc5a536a2016-02-16 14:52:39 +010096
stevenvanrossemc3a344f2016-11-04 19:34:47 +010097 # default switch configuration
98 enable_ryu_learning = False
peusterm72f09882018-05-15 17:10:27 +020099 if enable_learning:
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100100 self.failMode = 'standalone'
101 enable_ryu_learning = True
102 else:
103 self.failMode = 'secure'
104
peustermde14f332016-03-15 16:14:21 +0100105 # Ryu management
peustermde14f332016-03-15 16:14:21 +0100106 if controller == RemoteController:
107 # start Ryu controller
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100108 self.startRyu(learning_switch=enable_ryu_learning)
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100109
peustermde14f332016-03-15 16:14:21 +0100110 # add the specified controller
peustermef6629e2016-03-14 17:21:56 +0100111 self.addController('c0', controller=controller)
112
113 # graph of the complete DC network
stevenvanrossemc1149022016-04-11 01:16:44 +0200114 self.DCNetwork_graph = nx.MultiDiGraph()
peustermef6629e2016-03-14 17:21:56 +0100115
stevenvanrossem461941c2016-05-10 11:41:29 +0200116 # initialize pool of vlan tags to setup the SDN paths
peusterm54ebd582017-07-19 12:11:55 -0700117 self.vlans = range(1, 4095)[::-1]
stevenvanrossem461941c2016-05-10 11:41:29 +0200118
stevenvanrossem27b6d952016-05-10 16:37:57 +0200119 # link to Ryu REST_API
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200120 ryu_ip = 'localhost'
stevenvanrossem27b6d952016-05-10 16:37:57 +0200121 ryu_port = '8080'
122 self.ryu_REST_api = 'http://{0}:{1}'.format(ryu_ip, ryu_port)
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200123 self.RyuSession = requests.Session()
stevenvanrossem27b6d952016-05-10 16:37:57 +0200124
peustermef6629e2016-03-14 17:21:56 +0100125 # monitoring agent
stevenvanrossem60670da2016-04-15 15:31:28 +0200126 if monitor:
127 self.monitor_agent = DCNetworkMonitor(self)
128 else:
129 self.monitor_agent = None
peustermef6629e2016-03-14 17:21:56 +0100130
peusterm42f08be2016-03-10 21:56:34 +0100131 # initialize resource model registrar
peusterma4d84792016-03-25 12:27:07 +0100132 self.rm_registrar = ResourceModelRegistrar(
133 dc_emulation_max_cpu, dc_emulation_max_mem)
stevenvanrossemb3f34172016-11-16 23:30:57 +0100134 self.cpu_period = CPU_PERIOD
peustermcbcd4c22015-12-28 11:33:42 +0100135
peusterm60bf8b82016-04-06 14:12:35 +0200136 def addDatacenter(self, label, metadata={}, resource_log_path=None):
peustermcbcd4c22015-12-28 11:33:42 +0100137 """
138 Create and add a logical cloud data center to the network.
139 """
peusterma47db032016-02-04 14:55:29 +0100140 if label in self.dcs:
141 raise Exception("Data center label already exists: %s" % label)
peusterm72f09882018-05-15 17:10:27 +0200142 dc = Datacenter(label, metadata=metadata,
143 resource_log_path=resource_log_path)
peustermcbcd4c22015-12-28 11:33:42 +0100144 dc.net = self # set reference to network
peusterma47db032016-02-04 14:55:29 +0100145 self.dcs[label] = dc
peustermcbcd4c22015-12-28 11:33:42 +0100146 dc.create() # finally create the data center in our Mininet instance
peustermf9a817d2016-07-18 09:06:04 +0200147 LOG.info("added data center: %s" % label)
peustermcbcd4c22015-12-28 11:33:42 +0100148 return dc
149
peusterme6092692016-01-11 16:32:58 +0100150 def addLink(self, node1, node2, **params):
peusterm5b844a12016-01-11 15:58:15 +0100151 """
152 Able to handle Datacenter objects as link
153 end points.
154 """
peustermcbcd4c22015-12-28 11:33:42 +0100155 assert node1 is not None
156 assert node2 is not None
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100157
peustermcbcd4c22015-12-28 11:33:42 +0100158 # ensure type of node1
peusterm72f09882018-05-15 17:10:27 +0200159 if isinstance(node1, basestring):
peustermcbcd4c22015-12-28 11:33:42 +0100160 if node1 in self.dcs:
161 node1 = self.dcs[node1].switch
peusterm72f09882018-05-15 17:10:27 +0200162 if isinstance(node1, Datacenter):
peustermcbcd4c22015-12-28 11:33:42 +0100163 node1 = node1.switch
164 # ensure type of node2
peusterm72f09882018-05-15 17:10:27 +0200165 if isinstance(node2, basestring):
peustermcbcd4c22015-12-28 11:33:42 +0100166 if node2 in self.dcs:
167 node2 = self.dcs[node2].switch
peusterm72f09882018-05-15 17:10:27 +0200168 if isinstance(node2, Datacenter):
peustermcbcd4c22015-12-28 11:33:42 +0100169 node2 = node2.switch
peustermc3b977e2016-01-12 10:09:35 +0100170 # try to give containers a default IP
peusterm72f09882018-05-15 17:10:27 +0200171 if isinstance(node1, Docker):
peustermea8db832016-03-08 10:25:58 +0100172 if "params1" not in params:
peustermc3b977e2016-01-12 10:09:35 +0100173 params["params1"] = {}
peustermea8db832016-03-08 10:25:58 +0100174 if "ip" not in params["params1"]:
peustermc3b977e2016-01-12 10:09:35 +0100175 params["params1"]["ip"] = self.getNextIp()
peusterm72f09882018-05-15 17:10:27 +0200176 if isinstance(node2, Docker):
peustermea8db832016-03-08 10:25:58 +0100177 if "params2" not in params:
peustermc3b977e2016-01-12 10:09:35 +0100178 params["params2"] = {}
peustermea8db832016-03-08 10:25:58 +0100179 if "ip" not in params["params2"]:
peustermc3b977e2016-01-12 10:09:35 +0100180 params["params2"]["ip"] = self.getNextIp()
peustermea8db832016-03-08 10:25:58 +0100181 # ensure that we allow TCLinks between data centers
182 # TODO this is not optimal, we use cls=Link for containers and TCLink for data centers
peusterm72f09882018-05-15 17:10:27 +0200183 # see Containernet issue:
184 # https://github.com/mpeuster/containernet/issues/3
peustermea8db832016-03-08 10:25:58 +0100185 if "cls" not in params:
186 params["cls"] = TCLink
peustermc3b977e2016-01-12 10:09:35 +0100187
peusterm5877ea22016-05-11 13:44:59 +0200188 link = Containernet.addLink(self, node1, node2, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100189
stevenvanrossemc1149022016-04-11 01:16:44 +0200190 # try to give container interfaces a default id
191 node1_port_id = node1.ports[link.intf1]
192 if isinstance(node1, Docker):
193 if "id" in params["params1"]:
194 node1_port_id = params["params1"]["id"]
stevenvanrossem5b376412016-05-04 15:34:49 +0200195 node1_port_name = link.intf1.name
stevenvanrossemc1149022016-04-11 01:16:44 +0200196
197 node2_port_id = node2.ports[link.intf2]
198 if isinstance(node2, Docker):
199 if "id" in params["params2"]:
200 node2_port_id = params["params2"]["id"]
stevenvanrossem5b376412016-05-04 15:34:49 +0200201 node2_port_name = link.intf2.name
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200202
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100203 # add edge and assigned port number to graph in both directions between node1 and node2
stevenvanrossemc1149022016-04-11 01:16:44 +0200204 # port_id: id given in descriptor (if available, otherwise same as port)
peusterm5877ea22016-05-11 13:44:59 +0200205 # port: portnumber assigned by Containernet
stevenvanrossemc1149022016-04-11 01:16:44 +0200206
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200207 attr_dict = {}
208 # possible weight metrics allowed by TClink class:
209 weight_metrics = ['bw', 'delay', 'jitter', 'loss']
210 edge_attributes = [p for p in params if p in weight_metrics]
211 for attr in edge_attributes:
212 # if delay: strip ms (need number as weight in graph)
joka27edb4f2017-01-17 12:40:59 +0100213 match = re.search('([0-9]*\.?[0-9]+)', str(params[attr]))
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200214 if match:
215 attr_number = match.group(1)
216 else:
217 attr_number = None
218 attr_dict[attr] = attr_number
219
stevenvanrossem5b376412016-05-04 15:34:49 +0200220 attr_dict2 = {'src_port_id': node1_port_id, 'src_port_nr': node1.ports[link.intf1],
221 'src_port_name': node1_port_name,
peusterm72f09882018-05-15 17:10:27 +0200222 'dst_port_id': node2_port_id, 'dst_port_nr': node2.ports[link.intf2],
stevenvanrossem5b376412016-05-04 15:34:49 +0200223 'dst_port_name': node2_port_name}
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200224 attr_dict2.update(attr_dict)
peusterm72f09882018-05-15 17:10:27 +0200225 self.DCNetwork_graph.add_edge(
226 node1.name, node2.name, attr_dict=attr_dict2)
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200227
stevenvanrossem5b376412016-05-04 15:34:49 +0200228 attr_dict2 = {'src_port_id': node2_port_id, 'src_port_nr': node2.ports[link.intf2],
229 'src_port_name': node2_port_name,
peusterm72f09882018-05-15 17:10:27 +0200230 'dst_port_id': node1_port_id, 'dst_port_nr': node1.ports[link.intf1],
stevenvanrossem5b376412016-05-04 15:34:49 +0200231 'dst_port_name': node1_port_name}
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200232 attr_dict2.update(attr_dict)
peusterm72f09882018-05-15 17:10:27 +0200233 self.DCNetwork_graph.add_edge(
234 node2.name, node1.name, attr_dict=attr_dict2)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100235
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100236 LOG.debug("addLink: n1={0} intf1={1} -- n2={2} intf2={3}".format(
peusterm72f09882018-05-15 17:10:27 +0200237 str(node1), node1_port_name, str(node2), node2_port_name))
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100238
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100239 return link
peustermcbcd4c22015-12-28 11:33:42 +0100240
stevenvanrossem00e65b92017-04-18 16:57:40 +0200241 def removeLink(self, link=None, node1=None, node2=None):
242 """
243 Remove the link from the Containernet and the networkx graph
244 """
peusterm92a37cd2017-05-17 09:51:05 +0200245 if link is not None:
246 node1 = link.intf1.node
247 node2 = link.intf2.node
248 assert node1 is not None
249 assert node2 is not None
stevenvanrossem00e65b92017-04-18 16:57:40 +0200250 Containernet.removeLink(self, link=link, node1=node1, node2=node2)
peusterm92a37cd2017-05-17 09:51:05 +0200251 # TODO we might decrease the loglevel to debug:
252 try:
253 self.DCNetwork_graph.remove_edge(node2.name, node1.name)
peusterm72f09882018-05-15 17:10:27 +0200254 except BaseException:
255 LOG.warning("%s, %s not found in DCNetwork_graph." %
256 ((node2.name, node1.name)))
peusterm92a37cd2017-05-17 09:51:05 +0200257 try:
258 self.DCNetwork_graph.remove_edge(node1.name, node2.name)
peusterm72f09882018-05-15 17:10:27 +0200259 except BaseException:
260 LOG.warning("%s, %s not found in DCNetwork_graph." %
261 ((node1.name, node2.name)))
stevenvanrossem00e65b92017-04-18 16:57:40 +0200262
peusterm72f09882018-05-15 17:10:27 +0200263 def addDocker(self, label, **params):
peusterm5b844a12016-01-11 15:58:15 +0100264 """
peusterm293cbc32016-01-13 17:05:28 +0100265 Wrapper for addDocker method to use custom container class.
peusterm5b844a12016-01-11 15:58:15 +0100266 """
stevenvanrossemf3712012017-05-04 00:01:52 +0200267 self.DCNetwork_graph.add_node(label, type=params.get('type', 'docker'))
peusterm72f09882018-05-15 17:10:27 +0200268 return Containernet.addDocker(
269 self, label, cls=EmulatorCompute, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100270
peusterm72f09882018-05-15 17:10:27 +0200271 def removeDocker(self, label, **params):
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100272 """
273 Wrapper for removeDocker method to update graph.
274 """
stevenvanrossem8fbf9782016-02-17 11:40:23 +0100275 self.DCNetwork_graph.remove_node(label)
peusterm5877ea22016-05-11 13:44:59 +0200276 return Containernet.removeDocker(self, label, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100277
stevenvanrossemf3712012017-05-04 00:01:52 +0200278 def addExtSAP(self, sap_name, sap_ip, **params):
279 """
280 Wrapper for addExtSAP method to store SAP also in graph.
281 """
282 # make sure that 'type' is set
peusterm72f09882018-05-15 17:10:27 +0200283 params['type'] = params.get('type', 'sap_ext')
stevenvanrossemf3712012017-05-04 00:01:52 +0200284 self.DCNetwork_graph.add_node(sap_name, type=params['type'])
stevenvanrossemf3712012017-05-04 00:01:52 +0200285 return Containernet.addExtSAP(self, sap_name, sap_ip, **params)
286
287 def removeExtSAP(self, sap_name, **params):
288 """
289 Wrapper for removeExtSAP method to remove SAP also from graph.
290 """
291 self.DCNetwork_graph.remove_node(sap_name)
292 return Containernet.removeExtSAP(self, sap_name)
293
peusterm72f09882018-05-15 17:10:27 +0200294 def addSwitch(self, name, add_to_graph=True, **params):
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100295 """
296 Wrapper for addSwitch method to store switch also in graph.
297 """
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100298
299 # add this switch to the global topology overview
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100300 if add_to_graph:
peusterm72f09882018-05-15 17:10:27 +0200301 self.DCNetwork_graph.add_node(
302 name, type=params.get('type', 'switch'))
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100303
304 # set the learning switch behavior
peusterm72f09882018-05-15 17:10:27 +0200305 if 'failMode' in params:
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100306 failMode = params['failMode']
peusterm72f09882018-05-15 17:10:27 +0200307 else:
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100308 failMode = self.failMode
309
peusterm72f09882018-05-15 17:10:27 +0200310 s = Containernet.addSwitch(
311 self, name, protocols='OpenFlow10,OpenFlow12,OpenFlow13', failMode=failMode, **params)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100312
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100313 return s
peustermc3b977e2016-01-12 10:09:35 +0100314
peustermbd44f4a2016-01-13 14:53:30 +0100315 def getAllContainers(self):
316 """
317 Returns a list with all containers within all data centers.
318 """
319 all_containers = []
320 for dc in self.dcs.itervalues():
321 all_containers += dc.listCompute()
322 return all_containers
323
peustermcbcd4c22015-12-28 11:33:42 +0100324 def start(self):
325 # start
326 for dc in self.dcs.itervalues():
327 dc.start()
peusterm5877ea22016-05-11 13:44:59 +0200328 Containernet.start(self)
peustermcbcd4c22015-12-28 11:33:42 +0100329
330 def stop(self):
stevenvanrossem60670da2016-04-15 15:31:28 +0200331
stevenvanrossemc6abf132016-04-14 11:15:58 +0200332 # stop the monitor agent
stevenvanrossem60670da2016-04-15 15:31:28 +0200333 if self.monitor_agent is not None:
334 self.monitor_agent.stop()
peustermcbcd4c22015-12-28 11:33:42 +0100335
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200336 # stop emulator net
peusterm5877ea22016-05-11 13:44:59 +0200337 Containernet.stop(self)
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200338
339 # stop Ryu controller
peusterm8b04b532016-07-19 16:55:38 +0200340 self.killRyu()
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200341
peustermcbcd4c22015-12-28 11:33:42 +0100342 def CLI(self):
peusterm293cbc32016-01-13 17:05:28 +0100343 CLI(self)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100344
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100345 def setLAN(self, vnf_list):
346 """
347 setup an E-LAN network by assigning the same VLAN tag to each DC interface of the VNFs in the E-LAN
348
349 :param vnf_list: names of the VNFs in this E-LAN [{name:,interface:},...]
350 :return:
351 """
352 src_sw = None
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100353 src_sw_inport_name = None
354
355 # get a vlan tag for this E-LAN
356 vlan = self.vlans.pop()
357
358 for vnf in vnf_list:
359 vnf_src_name = vnf['name']
360 vnf_src_interface = vnf['interface']
361
362 # check if port is specified (vnf:port)
363 if vnf_src_interface is None:
364 # take first interface by default
365 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
366 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
367 vnf_src_interface = link_dict[0]['src_port_id']
368
369 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
370 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
371 for link in link_dict:
372 if (link_dict[link]['src_port_id'] == vnf_src_interface or
peusterm72f09882018-05-15 17:10:27 +0200373 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 +0100374 # found the right link and connected switch
375 src_sw = connected_sw
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100376 src_sw_inport_name = link_dict[link]['dst_port_name']
377 break
378
379 # set the tag on the dc switch interface
peusterm72f09882018-05-15 17:10:27 +0200380 LOG.debug('set E-LAN: vnf name: {0} interface: {1} tag: {2}'.format(
381 vnf_src_name, vnf_src_interface, vlan))
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100382 switch_node = self.getNodeByName(src_sw)
383 self._set_vlan_tag(switch_node, src_sw_inport_name, vlan)
384
stevenvanrossembecc7c52016-11-07 05:52:01 +0100385 def _addMonitorFlow(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None,
peusterm72f09882018-05-15 17:10:27 +0200386 tag=None, **kwargs):
stevenvanrossembf1754e2016-11-17 10:20:52 +0100387 """
388 Add a monitoring flow entry that adds a special flowentry/counter at the begin or end of a chain.
389 So this monitoring flowrule exists on top of a previously defined chain rule and uses the same vlan tag/routing.
390 :param vnf_src_name:
391 :param vnf_dst_name:
392 :param vnf_src_interface:
393 :param vnf_dst_interface:
394 :param tag: vlan tag to be used for this chain (same tag as existing chain)
395 :param monitor_placement: 'tx' or 'rx' indicating to place the extra flowentry resp. at the beginning or end of the chain
396 :return:
397 """
stevenvanrossembecc7c52016-11-07 05:52:01 +0100398
399 src_sw = None
400 src_sw_inport_nr = 0
401 src_sw_inport_name = None
402 dst_sw = None
403 dst_sw_outport_nr = 0
404 dst_sw_outport_name = None
405
406 LOG.debug("call AddMonitorFlow vnf_src_name=%r, vnf_src_interface=%r, vnf_dst_name=%r, vnf_dst_interface=%r",
407 vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface)
408
peusterm72f09882018-05-15 17:10:27 +0200409 # check if port is specified (vnf:port)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100410 if vnf_src_interface is None:
411 # take first interface by default
412 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
413 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
414 vnf_src_interface = link_dict[0]['src_port_id']
415
416 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
417 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
418 for link in link_dict:
419 if (link_dict[link]['src_port_id'] == vnf_src_interface or
420 link_dict[link]['src_port_name'] == vnf_src_interface): # Fix: we might also get interface names, e.g, from a son-emu-cli call
421 # found the right link and connected switch
422 src_sw = connected_sw
423 src_sw_inport_nr = link_dict[link]['dst_port_nr']
424 src_sw_inport_name = link_dict[link]['dst_port_name']
425 break
426
427 if vnf_dst_interface is None:
428 # take first interface by default
429 connected_sw = self.DCNetwork_graph.neighbors(vnf_dst_name)[0]
430 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
431 vnf_dst_interface = link_dict[0]['dst_port_id']
432
433 vnf_dst_name = vnf_dst_name.split(':')[0]
434 for connected_sw in self.DCNetwork_graph.neighbors(vnf_dst_name):
435 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
436 for link in link_dict:
437 if link_dict[link]['dst_port_id'] == vnf_dst_interface or \
438 link_dict[link]['dst_port_name'] == vnf_dst_interface: # Fix: we might also get interface names, e.g, from a son-emu-cli call
439 # found the right link and connected switch
440 dst_sw = connected_sw
441 dst_sw_outport_nr = link_dict[link]['src_port_nr']
442 dst_sw_outport_name = link_dict[link]['src_port_name']
443 break
444
445 if not tag >= 0:
446 LOG.exception('tag not valid: {0}'.format(tag))
447
448 # get shortest path
449 try:
450 # returns the first found shortest path
451 # if all shortest paths are wanted, use: all_shortest_paths
peusterm72f09882018-05-15 17:10:27 +0200452 path = nx.shortest_path(
453 self.DCNetwork_graph, src_sw, dst_sw, weight=kwargs.get('weight'))
454 except BaseException:
stevenvanrossembecc7c52016-11-07 05:52:01 +0100455 LOG.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
456 vnf_src_name, vnf_dst_name, src_sw, dst_sw))
457 LOG.debug("Graph nodes: %r" % self.DCNetwork_graph.nodes())
458 LOG.debug("Graph edges: %r" % self.DCNetwork_graph.edges())
459 for e, v in self.DCNetwork_graph.edges():
460 LOG.debug("%r" % self.DCNetwork_graph[e][v])
peusterm72f09882018-05-15 17:10:27 +0200461 return "No path could be found between {0} and {1}".format(
462 vnf_src_name, vnf_dst_name)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100463
peusterm37911562018-10-18 15:03:55 +0200464 LOG.debug("Creating path between {0} and {1}: {2}".format(
peusterm72f09882018-05-15 17:10:27 +0200465 vnf_src_name, vnf_dst_name, path))
stevenvanrossembecc7c52016-11-07 05:52:01 +0100466
467 current_hop = src_sw
468 switch_inport_nr = src_sw_inport_nr
469
470 cmd = kwargs.get('cmd')
471
peusterm72f09882018-05-15 17:10:27 +0200472 # iterate through the path to install the flow-entries
473 for i in range(0, len(path)):
stevenvanrossembecc7c52016-11-07 05:52:01 +0100474 current_node = self.getNodeByName(current_hop)
475
peusterm72f09882018-05-15 17:10:27 +0200476 if path.index(current_hop) < len(path) - 1:
477 next_hop = path[path.index(current_hop) + 1]
stevenvanrossembecc7c52016-11-07 05:52:01 +0100478 else:
peusterm72f09882018-05-15 17:10:27 +0200479 # last switch reached
stevenvanrossembecc7c52016-11-07 05:52:01 +0100480 next_hop = vnf_dst_name
481
482 next_node = self.getNodeByName(next_hop)
483
484 if next_hop == vnf_dst_name:
485 switch_outport_nr = dst_sw_outport_nr
peusterm37911562018-10-18 15:03:55 +0200486 LOG.debug("end node reached: {0}".format(vnf_dst_name))
peusterm72f09882018-05-15 17:10:27 +0200487 elif not isinstance(next_node, OVSSwitch):
stevenvanrossembecc7c52016-11-07 05:52:01 +0100488 LOG.info("Next node: {0} is not a switch".format(next_hop))
489 return "Next node: {0} is not a switch".format(next_hop)
490 else:
491 # take first link between switches by default
492 index_edge_out = 0
493 switch_outport_nr = self.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100494
peusterm72f09882018-05-15 17:10:27 +0200495 # set of entry via ovs-ofctl
496 if isinstance(current_node, OVSSwitch):
stevenvanrossembecc7c52016-11-07 05:52:01 +0100497 kwargs['vlan'] = tag
498 kwargs['path'] = path
499 kwargs['current_hop'] = current_hop
500 kwargs['switch_inport_name'] = src_sw_inport_name
501 kwargs['switch_outport_name'] = dst_sw_outport_name
502 kwargs['skip_vlan_tag'] = True
stevenvanrossem263eee52017-02-08 01:04:36 +0100503 kwargs['pathindex'] = i
stevenvanrossembecc7c52016-11-07 05:52:01 +0100504
stevenvanrossem4fac2af2016-12-22 01:26:02 +0100505 monitor_placement = kwargs.get('monitor_placement').strip()
stevenvanrossembecc7c52016-11-07 05:52:01 +0100506 # put monitor flow at the dst switch
507 insert_flow = False
peusterm72f09882018-05-15 17:10:27 +0200508 # first node:
509 if monitor_placement == 'tx' and path.index(current_hop) == 0:
stevenvanrossembecc7c52016-11-07 05:52:01 +0100510 insert_flow = True
511 # put monitoring flow at the src switch
peusterm72f09882018-05-15 17:10:27 +0200512 # last node:
513 elif monitor_placement == 'rx' and path.index(current_hop) == len(path) - 1:
stevenvanrossembecc7c52016-11-07 05:52:01 +0100514 insert_flow = True
stevenvanrossem4fac2af2016-12-22 01:26:02 +0100515 elif monitor_placement not in ['rx', 'tx']:
peusterm72f09882018-05-15 17:10:27 +0200516 LOG.exception(
517 'invalid monitor command: {0}'.format(monitor_placement))
stevenvanrossembecc7c52016-11-07 05:52:01 +0100518
519 if self.controller == RemoteController and insert_flow:
peusterm72f09882018-05-15 17:10:27 +0200520 # set flow entry via ryu rest api
521 self._set_flow_entry_ryu_rest(
522 current_node, switch_inport_nr, switch_outport_nr, **kwargs)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100523 break
524 elif insert_flow:
peusterm72f09882018-05-15 17:10:27 +0200525 # set flow entry via ovs-ofctl
526 self._set_flow_entry_dpctl(
527 current_node, switch_inport_nr, switch_outport_nr, **kwargs)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100528 break
529
530 # take first link between switches by default
peusterm72f09882018-05-15 17:10:27 +0200531 if isinstance(next_node, OVSSwitch):
stevenvanrossembecc7c52016-11-07 05:52:01 +0100532 switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
533 current_hop = next_hop
534
peusterm72f09882018-05-15 17:10:27 +0200535 return "path {2} between {0} and {1}".format(
536 vnf_src_name, vnf_dst_name, cmd)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100537
peusterm72f09882018-05-15 17:10:27 +0200538 def setChain(self, vnf_src_name, vnf_dst_name,
539 vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200540 """
541 Chain 2 vnf interfaces together by installing the flowrules in the switches along their path.
542 Currently the path is found using the default networkx shortest path function.
543 Each chain gets a unique vlan id , so different chains wil not interfere.
544
545 :param vnf_src_name: vnf name (string)
546 :param vnf_dst_name: vnf name (string)
547 :param vnf_src_interface: source interface name (string)
548 :param vnf_dst_interface: destination interface name (string)
549 :param cmd: 'add-flow' (default) to add a chain, 'del-flows' to remove a chain
550 :param cookie: cookie for the installed flowrules (can be used later as identifier for a set of installed chains)
551 :param match: custom match entry to be added to the flowrules (default: only in_port and vlan tag)
552 :param priority: custom flowrule priority
stevenvanrossembf1754e2016-11-17 10:20:52 +0100553 :param monitor: boolean to indicate whether this chain is a monitoring chain
554 :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 +0100555 :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 +0100556 :param path: custom path between the two VNFs (list of switches)
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200557 :return: output log string
558 """
stevenvanrossembecc7c52016-11-07 05:52:01 +0100559
560 # special procedure for monitoring flows
561 if kwargs.get('monitor'):
562
563 # check if chain already exists
564 found_chains = [chain_dict for chain_dict in self.installed_chains if
peusterm72f09882018-05-15 17:10:27 +0200565 (chain_dict['vnf_src_name'] == vnf_src_name and
566 chain_dict['vnf_src_interface'] == vnf_src_interface and
567 chain_dict['vnf_dst_name'] == vnf_dst_name and
568 chain_dict['vnf_dst_interface'] == vnf_dst_interface)]
stevenvanrossembecc7c52016-11-07 05:52:01 +0100569
570 if len(found_chains) > 0:
571 # this chain exists, so need an extra monitoring flow
572 # assume only 1 chain per vnf/interface pair
573 LOG.debug('*** installing monitoring chain on top of pre-defined chain from {0}:{1} -> {2}:{3}'.
peusterm72f09882018-05-15 17:10:27 +0200574 format(vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface))
stevenvanrossembecc7c52016-11-07 05:52:01 +0100575 tag = found_chains[0]['tag']
576 ret = self._addMonitorFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface,
peusterm72f09882018-05-15 17:10:27 +0200577 tag=tag, table_id=0, **kwargs)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100578 return ret
579 else:
580 # no chain existing (or E-LAN) -> install normal chain
stevenvanrossem4fac2af2016-12-22 01:26:02 +0100581 LOG.warning('*** installing monitoring chain without pre-defined NSD chain from {0}:{1} -> {2}:{3}'.
stevenvanrossembecc7c52016-11-07 05:52:01 +0100582 format(vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface))
583 pass
584
splietker7b38ee12017-06-28 17:24:01 +0200585 cmd = kwargs.get('cmd', 'add-flow')
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100586 if cmd == 'add-flow' or cmd == 'del-flows':
peusterm72f09882018-05-15 17:10:27 +0200587 ret = self._chainAddFlow(
588 vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface, **kwargs)
stevenvanrossem461941c2016-05-10 11:41:29 +0200589 if kwargs.get('bidirectional'):
jokac304ad32017-01-09 10:58:23 +0100590 if kwargs.get('path') is not None:
591 kwargs['path'] = list(reversed(kwargs.get('path')))
peusterm72f09882018-05-15 17:10:27 +0200592 ret = ret + '\n' + \
593 self._chainAddFlow(
594 vnf_dst_name, vnf_src_name, vnf_dst_interface, vnf_src_interface, **kwargs)
stevenvanrossem9315da42016-04-11 12:10:06 +0200595
stevenvanrossem461941c2016-05-10 11:41:29 +0200596 else:
stevenvanrossem81955a52016-05-12 14:34:12 +0200597 ret = "Command unknown"
598
599 return ret
stevenvanrossem461941c2016-05-10 11:41:29 +0200600
peusterm72f09882018-05-15 17:10:27 +0200601 def _chainAddFlow(self, vnf_src_name, vnf_dst_name,
602 vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
stevenvanrossem461941c2016-05-10 11:41:29 +0200603
peusterm53d3c142016-07-18 10:10:11 +0200604 src_sw = None
peusterm53d3c142016-07-18 10:10:11 +0200605 src_sw_inport_nr = 0
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100606 src_sw_inport_name = None
607 dst_sw = None
peusterm53d3c142016-07-18 10:10:11 +0200608 dst_sw_outport_nr = 0
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100609 dst_sw_outport_name = None
peusterm53d3c142016-07-18 10:10:11 +0200610
611 LOG.debug("call chainAddFlow vnf_src_name=%r, vnf_src_interface=%r, vnf_dst_name=%r, vnf_dst_interface=%r",
612 vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface)
613
peusterm72f09882018-05-15 17:10:27 +0200614 # check if port is specified (vnf:port)
stevenvanrossemed711fd2016-04-11 16:59:29 +0200615 if vnf_src_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200616 # take first interface by default
617 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
618 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200619 vnf_src_interface = link_dict[0]['src_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200620
stevenvanrossem9315da42016-04-11 12:10:06 +0200621 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
622 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
623 for link in link_dict:
peusterm53d3c142016-07-18 10:10:11 +0200624 if (link_dict[link]['src_port_id'] == vnf_src_interface or
625 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 +0200626 # found the right link and connected switch
stevenvanrossem9315da42016-04-11 12:10:06 +0200627 src_sw = connected_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200628 src_sw_inport_nr = link_dict[link]['dst_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100629 src_sw_inport_name = link_dict[link]['dst_port_name']
stevenvanrossem9315da42016-04-11 12:10:06 +0200630 break
631
stevenvanrossemed711fd2016-04-11 16:59:29 +0200632 if vnf_dst_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200633 # take first interface by default
634 connected_sw = self.DCNetwork_graph.neighbors(vnf_dst_name)[0]
635 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200636 vnf_dst_interface = link_dict[0]['dst_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200637
638 vnf_dst_name = vnf_dst_name.split(':')[0]
639 for connected_sw in self.DCNetwork_graph.neighbors(vnf_dst_name):
640 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
641 for link in link_dict:
peusterm53d3c142016-07-18 10:10:11 +0200642 if link_dict[link]['dst_port_id'] == vnf_dst_interface or \
643 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 +0200644 # found the right link and connected switch
645 dst_sw = connected_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200646 dst_sw_outport_nr = link_dict[link]['src_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100647 dst_sw_outport_name = link_dict[link]['src_port_name']
stevenvanrossem9315da42016-04-11 12:10:06 +0200648 break
649
jokac304ad32017-01-09 10:58:23 +0100650 path = kwargs.get('path')
651 if path is None:
652 # get shortest path
653 try:
654 # returns the first found shortest path
655 # if all shortest paths are wanted, use: all_shortest_paths
peusterm72f09882018-05-15 17:10:27 +0200656 path = nx.shortest_path(
657 self.DCNetwork_graph, src_sw, dst_sw, weight=kwargs.get('weight'))
658 except BaseException:
jokac304ad32017-01-09 10:58:23 +0100659 LOG.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
660 vnf_src_name, vnf_dst_name, src_sw, dst_sw))
661 LOG.debug("Graph nodes: %r" % self.DCNetwork_graph.nodes())
662 LOG.debug("Graph edges: %r" % self.DCNetwork_graph.edges())
663 for e, v in self.DCNetwork_graph.edges():
664 LOG.debug("%r" % self.DCNetwork_graph[e][v])
peusterm72f09882018-05-15 17:10:27 +0200665 return "No path could be found between {0} and {1}".format(
666 vnf_src_name, vnf_dst_name)
stevenvanrossem9315da42016-04-11 12:10:06 +0200667
peusterm37911562018-10-18 15:03:55 +0200668 LOG.debug("Creating path between {0} and {1}: {2}".format(
peusterm72f09882018-05-15 17:10:27 +0200669 vnf_src_name, vnf_dst_name, path))
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100670
stevenvanrossem9315da42016-04-11 12:10:06 +0200671 current_hop = src_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200672 switch_inport_nr = src_sw_inport_nr
stevenvanrossem9315da42016-04-11 12:10:06 +0200673
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100674 # choose free vlan
stevenvanrossem27b6d952016-05-10 16:37:57 +0200675 cmd = kwargs.get('cmd')
676 vlan = None
stevenvanrossem263eee52017-02-08 01:04:36 +0100677 if cmd == 'add-flow':
stevenvanrossembecc7c52016-11-07 05:52:01 +0100678 if kwargs.get('tag'):
679 # use pre-defined tag
680 vlan = kwargs.get('tag')
681 else:
682 vlan = self.vlans.pop()
stevenvanrossem461941c2016-05-10 11:41:29 +0200683
stevenvanrossembecc7c52016-11-07 05:52:01 +0100684 # store the used vlan tag to identify this chain
685 if not kwargs.get('monitor'):
686 chain_dict = {}
687 chain_dict['vnf_src_name'] = vnf_src_name
688 chain_dict['vnf_dst_name'] = vnf_dst_name
689 chain_dict['vnf_src_interface'] = vnf_src_interface
690 chain_dict['vnf_dst_interface'] = vnf_dst_interface
691 chain_dict['tag'] = vlan
692 self.installed_chains.append(chain_dict)
693
peusterm72f09882018-05-15 17:10:27 +0200694 # iterate through the path to install the flow-entries
695 for i in range(0, len(path)):
stevenvanrossem9315da42016-04-11 12:10:06 +0200696 current_node = self.getNodeByName(current_hop)
stevenvanrossem461941c2016-05-10 11:41:29 +0200697
jokac304ad32017-01-09 10:58:23 +0100698 if i < len(path) - 1:
699 next_hop = path[i + 1]
stevenvanrossem9315da42016-04-11 12:10:06 +0200700 else:
jokac304ad32017-01-09 10:58:23 +0100701 # last switch reached
stevenvanrossem9315da42016-04-11 12:10:06 +0200702 next_hop = vnf_dst_name
703
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100704 next_node = self.getNodeByName(next_hop)
705
706 if next_hop == vnf_dst_name:
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200707 switch_outport_nr = dst_sw_outport_nr
peusterm37911562018-10-18 15:03:55 +0200708 LOG.debug("end node reached: {0}".format(vnf_dst_name))
peusterm72f09882018-05-15 17:10:27 +0200709 elif not isinstance(next_node, OVSSwitch):
peustermf9a817d2016-07-18 09:06:04 +0200710 LOG.info("Next node: {0} is not a switch".format(next_hop))
stevenvanrossemeefea6c2016-02-17 12:03:26 +0100711 return "Next node: {0} is not a switch".format(next_hop)
stevenvanrossem9315da42016-04-11 12:10:06 +0200712 else:
stevenvanrossemed711fd2016-04-11 16:59:29 +0200713 # take first link between switches by default
stevenvanrossem9315da42016-04-11 12:10:06 +0200714 index_edge_out = 0
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200715 switch_outport_nr = self.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
stevenvanrossem9315da42016-04-11 12:10:06 +0200716
peusterm72f09882018-05-15 17:10:27 +0200717 # set OpenFlow entry
718 if isinstance(current_node, OVSSwitch):
stevenvanrossem461941c2016-05-10 11:41:29 +0200719 kwargs['vlan'] = vlan
720 kwargs['path'] = path
721 kwargs['current_hop'] = current_hop
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100722 kwargs['switch_inport_name'] = src_sw_inport_name
723 kwargs['switch_outport_name'] = dst_sw_outport_name
jokac304ad32017-01-09 10:58:23 +0100724 kwargs['pathindex'] = i
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200725
726 if self.controller == RemoteController:
peusterm72f09882018-05-15 17:10:27 +0200727 # set flow entry via ryu rest api
728 self._set_flow_entry_ryu_rest(
729 current_node, switch_inport_nr, switch_outport_nr, **kwargs)
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200730 else:
peusterm72f09882018-05-15 17:10:27 +0200731 # set flow entry via ovs-ofctl
732 self._set_flow_entry_dpctl(
733 current_node, switch_inport_nr, switch_outport_nr, **kwargs)
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200734
stevenvanrossemed711fd2016-04-11 16:59:29 +0200735 # take first link between switches by default
peusterm72f09882018-05-15 17:10:27 +0200736 if isinstance(next_node, OVSSwitch):
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200737 switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
stevenvanrossemed711fd2016-04-11 16:59:29 +0200738 current_hop = next_hop
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100739
stevenvanrossemae588012017-06-06 10:33:19 +0200740 flow_options = {
peusterm72f09882018-05-15 17:10:27 +0200741 'priority': kwargs.get('priority', DEFAULT_PRIORITY),
742 'cookie': kwargs.get('cookie', DEFAULT_COOKIE),
743 'vlan': kwargs['vlan'],
744 'path': kwargs['path'],
745 'match_input': kwargs.get('match')
stevenvanrossemae588012017-06-06 10:33:19 +0200746 }
747 flow_options_str = json.dumps(flow_options, indent=1)
peusterm37911562018-10-18 15:03:55 +0200748 LOG.info("Installed flow rule: ({}:{}) -> ({}:{}) with options: {}"
749 .format(vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface, flow_options))
peusterm72f09882018-05-15 17:10:27 +0200750 return "success: {2} between {0} and {1} with options: {3}".format(
751 vnf_src_name, vnf_dst_name, cmd, flow_options_str)
stevenvanrossem27b6d952016-05-10 16:37:57 +0200752
peusterm72f09882018-05-15 17:10:27 +0200753 def _set_flow_entry_ryu_rest(
754 self, node, switch_inport_nr, switch_outport_nr, **kwargs):
stevenvanrossem27b6d952016-05-10 16:37:57 +0200755 match = 'in_port=%s' % switch_inport_nr
756
757 cookie = kwargs.get('cookie')
758 match_input = kwargs.get('match')
759 cmd = kwargs.get('cmd')
760 path = kwargs.get('path')
jokac304ad32017-01-09 10:58:23 +0100761 index = kwargs.get('pathindex')
schillinge83d22492019-02-20 11:04:08 +0100762 mod_dl_dst = kwargs.get('mod_dl_dst')
jokac304ad32017-01-09 10:58:23 +0100763
stevenvanrossem27b6d952016-05-10 16:37:57 +0200764 vlan = kwargs.get('vlan')
stevenvanrossemae588012017-06-06 10:33:19 +0200765 priority = kwargs.get('priority', DEFAULT_PRIORITY)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100766 # flag to not set the ovs port vlan tag
767 skip_vlan_tag = kwargs.get('skip_vlan_tag')
768 # table id to put this flowentry
769 table_id = kwargs.get('table_id')
770 if not table_id:
771 table_id = 0
stevenvanrossem27b6d952016-05-10 16:37:57 +0200772
773 s = ','
774 if match_input:
775 match = s.join([match, match_input])
776
777 flow = {}
778 flow['dpid'] = int(node.dpid, 16)
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200779
stevenvanrossem27b6d952016-05-10 16:37:57 +0200780 if cookie:
781 flow['cookie'] = int(cookie)
stevenvanrossem61699eb2016-08-05 15:57:59 +0200782 if priority:
783 flow['priority'] = int(priority)
stevenvanrossem27b6d952016-05-10 16:37:57 +0200784
stevenvanrossembecc7c52016-11-07 05:52:01 +0100785 flow['table_id'] = table_id
786
stevenvanrossem27b6d952016-05-10 16:37:57 +0200787 flow['actions'] = []
788
789 # possible Ryu actions, match fields:
790 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#add-a-flow-entry
791 if cmd == 'add-flow':
792 prefix = 'stats/flowentry/add'
peusterm72f09882018-05-15 17:10:27 +0200793 if vlan is not None:
jokac304ad32017-01-09 10:58:23 +0100794 if index == 0: # first node
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100795 # set vlan tag in ovs instance (to isolate E-LANs)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100796 if not skip_vlan_tag:
797 in_port_name = kwargs.get('switch_inport_name')
798 self._set_vlan_tag(node, in_port_name, vlan)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100799 # set vlan push action if more than 1 switch in the path
800 if len(path) > 1:
801 action = {}
peusterm72f09882018-05-15 17:10:27 +0200802 # Push a new VLAN tag if a input frame is
803 # non-VLAN-tagged
804 action['type'] = 'PUSH_VLAN'
805 # Ethertype 0x8100(=33024): IEEE 802.1Q VLAN-tagged
806 # frame
807 action['ethertype'] = 33024
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100808 flow['actions'].append(action)
809 action = {}
810 action['type'] = 'SET_FIELD'
811 action['field'] = 'vlan_vid'
stevenvanrossem9cc73602017-01-27 23:37:29 +0100812 # ryu expects the field to be masked
813 action['value'] = vlan | 0x1000
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100814 flow['actions'].append(action)
815
stevenvanrossem9cc73602017-01-27 23:37:29 +0100816 elif index == len(path) - 1: # last node
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100817 # set vlan tag in ovs instance (to isolate E-LANs)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100818 if not skip_vlan_tag:
819 out_port_name = kwargs.get('switch_outport_name')
820 self._set_vlan_tag(node, out_port_name, vlan)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100821 # set vlan pop action if more than 1 switch in the path
822 if len(path) > 1:
823 match += ',dl_vlan=%s' % vlan
824 action = {}
825 action['type'] = 'POP_VLAN'
826 flow['actions'].append(action)
827
stevenvanrossem27b6d952016-05-10 16:37:57 +0200828 else: # middle nodes
stevenvanrossem27b6d952016-05-10 16:37:57 +0200829 match += ',dl_vlan=%s' % vlan
schillinge83d22492019-02-20 11:04:08 +0100830 if mod_dl_dst:
831 action = {}
832 action['type'] = 'SET_FIELD'
833 action['field'] = 'eth_dst'
834 action['value'] = mod_dl_dst
835 flow['actions'].append(action)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100836
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200837 # output action must come last
838 action = {}
839 action['type'] = 'OUTPUT'
840 action['port'] = switch_outport_nr
841 flow['actions'].append(action)
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200842
stevenvanrossem27b6d952016-05-10 16:37:57 +0200843 elif cmd == 'del-flows':
stevenvanrossem27b6d952016-05-10 16:37:57 +0200844 prefix = 'stats/flowentry/delete'
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200845
stevenvanrossem27b6d952016-05-10 16:37:57 +0200846 if cookie:
stevenvanrossem1ef77022016-05-12 16:36:10 +0200847 # TODO: add cookie_mask as argument
peusterm72f09882018-05-15 17:10:27 +0200848 # need full mask to match complete cookie
849 flow['cookie_mask'] = int('0xffffffffffffffff', 16)
stevenvanrossem27b6d952016-05-10 16:37:57 +0200850
851 action = {}
852 action['type'] = 'OUTPUT'
853 action['port'] = switch_outport_nr
854 flow['actions'].append(action)
855
856 flow['match'] = self._parse_match(match)
857 self.ryu_REST(prefix, data=flow)
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100858
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100859 def _set_vlan_tag(self, node, switch_port, tag):
peusterm72f09882018-05-15 17:10:27 +0200860 node.vsctl('set', 'port {0} tag={1}'.format(switch_port, tag))
861 LOG.debug("set vlan in switch: {0} in_port: {1} vlan tag: {2}".format(
862 node.name, switch_port, tag))
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100863
peusterm72f09882018-05-15 17:10:27 +0200864 def _set_flow_entry_dpctl(
865 self, node, switch_inport_nr, switch_outport_nr, **kwargs):
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100866
stevenvanrossem23c48092016-05-06 17:21:12 +0200867 match = 'in_port=%s' % switch_inport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200868
869 cookie = kwargs.get('cookie')
870 match_input = kwargs.get('match')
871 cmd = kwargs.get('cmd')
872 path = kwargs.get('path')
jokac304ad32017-01-09 10:58:23 +0100873 index = kwargs.get('pathindex')
stevenvanrossem461941c2016-05-10 11:41:29 +0200874 vlan = kwargs.get('vlan')
875
stevenvanrossem898a2af2016-05-06 18:28:57 +0200876 s = ','
877 if cookie:
878 cookie = 'cookie=%s' % cookie
879 match = s.join([cookie, match])
stevenvanrossem23c48092016-05-06 17:21:12 +0200880 if match_input:
stevenvanrossem23c48092016-05-06 17:21:12 +0200881 match = s.join([match, match_input])
stevenvanrossem23c48092016-05-06 17:21:12 +0200882 if cmd == 'add-flow':
883 action = 'action=%s' % switch_outport_nr
peusterm72f09882018-05-15 17:10:27 +0200884 if vlan is not None:
885 if index == 0: # first node
886 action = ('action=mod_vlan_vid:%s' % vlan) + \
887 (',output=%s' % switch_outport_nr)
stevenvanrossem461941c2016-05-10 11:41:29 +0200888 match = '-O OpenFlow13 ' + match
jokac304ad32017-01-09 10:58:23 +0100889 elif index == len(path) - 1: # last node
stevenvanrossem461941c2016-05-10 11:41:29 +0200890 match += ',dl_vlan=%s' % vlan
891 action = 'action=strip_vlan,output=%s' % switch_outport_nr
892 else: # middle nodes
893 match += ',dl_vlan=%s' % vlan
stevenvanrossem23c48092016-05-06 17:21:12 +0200894 ofcmd = s.join([match, action])
895 elif cmd == 'del-flows':
896 ofcmd = match
897 else:
898 ofcmd = ''
899
900 node.dpctl(cmd, ofcmd)
peustermf9a817d2016-07-18 09:06:04 +0200901 LOG.info("{3} in switch: {0} in_port: {1} out_port: {2}".format(node.name, switch_inport_nr,
peusterm72f09882018-05-15 17:10:27 +0200902 switch_outport_nr, cmd))
stevenvanrossem23c48092016-05-06 17:21:12 +0200903
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100904 # start Ryu Openflow controller as Remote Controller for the DCNetwork
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200905 def startRyu(self, learning_switch=True):
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100906 # start Ryu controller with rest-API
schillinge06dc1252019-01-31 10:00:02 +0100907
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100908 # ryu default learning switch
schillinge06dc1252019-01-31 10:00:02 +0100909 # ryu_learning_app = python_install_path + '/ryu/app/simple_switch_13.py'
peusterm72f09882018-05-15 17:10:27 +0200910 # custom learning switch that installs a default NORMAL action in the
911 # ovs switches
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100912 dir_path = os.path.dirname(os.path.realpath(__file__))
schillinge06dc1252019-01-31 10:00:02 +0100913 ryu_learning_app = dir_path + '/son_emu_simple_switch_13.py'
914 ryu_rest_app = 'ryu.app.ofctl_rest'
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100915 # change the default Openflow controller port to 6653 (official IANA-assigned port number), as used by Mininet
916 # Ryu still uses 6633 as default
917 ryu_option = '--ofp-tcp-listen-port'
918 ryu_of_port = '6653'
peustermde14f332016-03-15 16:14:21 +0100919 ryu_cmd = 'ryu-manager'
peustermef6629e2016-03-14 17:21:56 +0100920 FNULL = open("/tmp/ryu.log", 'w')
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200921 if learning_switch:
schillinge06dc1252019-01-31 10:00:02 +0100922 # learning and rest api
923 args = [ryu_cmd, ryu_learning_app, ryu_rest_app, ryu_option, ryu_of_port]
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200924 else:
stevenvanrossem73efd192016-06-29 01:44:07 +0200925 # no learning switch, but with rest api
schillinge06dc1252019-01-31 10:00:02 +0100926 args = [ryu_cmd, ryu_rest_app, ryu_option, ryu_of_port]
927 self.ryu_process = Popen(args, stdout=FNULL, stderr=FNULL)
928 LOG.debug('starting ryu-controller with %s' % args)
peusterm391773a2016-03-14 17:40:43 +0100929 time.sleep(1)
930
peusterm8b04b532016-07-19 16:55:38 +0200931 def killRyu(self):
932 """
933 Stop the Ryu controller that might be started by son-emu.
934 :return:
935 """
936 # try it nicely
peustermde14f332016-03-15 16:14:21 +0100937 if self.ryu_process is not None:
peusterm391773a2016-03-14 17:40:43 +0100938 self.ryu_process.terminate()
939 self.ryu_process.kill()
peusterm8b04b532016-07-19 16:55:38 +0200940 # ensure its death ;-)
stevenvanrossem89706802016-07-19 02:54:45 +0200941 Popen(['pkill', '-f', 'ryu-manager'])
peusterm391773a2016-03-14 17:40:43 +0100942
stevenvanrossem27b6d952016-05-10 16:37:57 +0200943 def ryu_REST(self, prefix, dpid=None, data=None):
stevenvanrossem27b6d952016-05-10 16:37:57 +0200944
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200945 if dpid:
946 url = self.ryu_REST_api + '/' + str(prefix) + '/' + str(dpid)
947 else:
948 url = self.ryu_REST_api + '/' + str(prefix)
949 if data:
950 req = self.RyuSession.post(url, json=data)
951 else:
952 req = self.RyuSession.get(url)
953
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200954 # do extra logging if status code is not 200 (OK)
955 if req.status_code is not requests.codes.ok:
956 logging.info(
957 'type {0} encoding: {1} text: {2} headers: {3} history: {4}'.format(req.headers['content-type'],
958 req.encoding, req.text,
959 req.headers, req.history))
960 LOG.info('url: {0}'.format(str(url)))
peusterm72f09882018-05-15 17:10:27 +0200961 if data:
962 LOG.info('POST: {0}'.format(str(data)))
963 LOG.info('status: {0} reason: {1}'.format(
964 req.status_code, req.reason))
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200965
966 if 'json' in req.headers['content-type']:
967 ret = req.json()
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200968 return ret
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200969
970 ret = req.text.rstrip()
971 return ret
972
stevenvanrossem27b6d952016-05-10 16:37:57 +0200973 # need to respect that some match fields must be integers
974 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#description-of-match-and-actions
peusterm72f09882018-05-15 17:10:27 +0200975
stevenvanrossem27b6d952016-05-10 16:37:57 +0200976 def _parse_match(self, match):
977 matches = match.split(',')
978 dict = {}
979 for m in matches:
980 match = m.split('=')
981 if len(match) == 2:
982 try:
983 m2 = int(match[1], 0)
peusterm72f09882018-05-15 17:10:27 +0200984 except BaseException:
stevenvanrossem27b6d952016-05-10 16:37:57 +0200985 m2 = match[1]
986
peusterm72f09882018-05-15 17:10:27 +0200987 dict.update({match[0]: m2})
stevenvanrossem27b6d952016-05-10 16:37:57 +0200988 return dict
989
peusterm72f09882018-05-15 17:10:27 +0200990 def find_connected_dc_interface(
991 self, vnf_src_name, vnf_src_interface=None):
stevenvanrossem17b6e882017-05-04 16:51:34 +0200992
993 if vnf_src_interface is None:
994 # take first interface by default
995 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
996 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
997 vnf_src_interface = link_dict[0]['src_port_id']
998
stevenvanrossem566779d2016-11-07 06:33:44 +0100999 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
1000 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
1001 for link in link_dict:
1002 if (link_dict[link]['src_port_id'] == vnf_src_interface or
peusterm72f09882018-05-15 17:10:27 +02001003 link_dict[link]['src_port_name'] == vnf_src_interface):
1004 # Fix: we might also get interface names, e.g, from a son-emu-cli call
stevenvanrossem566779d2016-11-07 06:33:44 +01001005 # found the right link and connected switch
stevenvanrossem566779d2016-11-07 06:33:44 +01001006 src_sw_inport_name = link_dict[link]['dst_port_name']
stevenvanrossem7062cee2016-12-22 10:31:38 +01001007 return src_sw_inport_name