blob: 1f30ba7abfcce8422fcd8189622944049d53edcd [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
stevenvanrossem9ebd0942016-02-22 10:13:05 +010028import site
peustermef6629e2016-03-14 17:21:56 +010029import time
stevenvanrossem9ebd0942016-02-22 10:13:05 +010030from subprocess import Popen
stevenvanrossem6b1d9b92016-05-02 13:10:40 +020031import re
stevenvanrossem3fc13932016-08-09 23:39:16 +020032import requests
stevenvanrossemc3a344f2016-11-04 19:34:47 +010033import os
stevenvanrossemae588012017-06-06 10:33:19 +020034import json
stevenvanrossem9ebd0942016-02-22 10:13:05 +010035
peusterm5877ea22016-05-11 13:44:59 +020036from mininet.net import Containernet
peusterm72f09882018-05-15 17:10:27 +020037from mininet.node import OVSSwitch, OVSKernelSwitch, Docker, RemoteController
peustermcbcd4c22015-12-28 11:33:42 +010038from mininet.cli import CLI
peustermea8db832016-03-08 10:25:58 +010039from mininet.link import TCLink
peusterm8b04b532016-07-19 16:55:38 +020040from mininet.clean import cleanup
stevenvanrossemc5a536a2016-02-16 14:52:39 +010041import networkx as nx
cgeoffroy9524ad32016-03-03 18:24:15 +010042from emuvim.dcemulator.monitoring import DCNetworkMonitor
peusterm72f09882018-05-15 17:10:27 +020043from emuvim.dcemulator.node import Datacenter, EmulatorCompute
peusterm42f08be2016-03-10 21:56:34 +010044from emuvim.dcemulator.resourcemodel import ResourceModelRegistrar
peustermcbcd4c22015-12-28 11:33:42 +010045
peustermf9a817d2016-07-18 09:06:04 +020046LOG = logging.getLogger("dcemulator.net")
47LOG.setLevel(logging.DEBUG)
48
stevenvanrossemb3f34172016-11-16 23:30:57 +010049# default CPU period used for cpu percentage-based cfs values (microseconds)
50CPU_PERIOD = 1000000
51
stevenvanrossemae588012017-06-06 10:33:19 +020052# default priority setting for added flow-rules
53DEFAULT_PRIORITY = 1000
54# default cookie number for new flow-rules
55DEFAULT_COOKIE = 10
56
peusterm72f09882018-05-15 17:10:27 +020057
peusterm5877ea22016-05-11 13:44:59 +020058class DCNetwork(Containernet):
peusterme4e89d32016-01-07 09:14:54 +010059 """
peusterm5877ea22016-05-11 13:44:59 +020060 Wraps the original Mininet/Containernet class and provides
peusterme4e89d32016-01-07 09:14:54 +010061 methods to add data centers, switches, etc.
62
63 This class is used by topology definition scripts.
64 """
peustermcbcd4c22015-12-28 11:33:42 +010065
peusterm0ec25102016-04-16 02:16:20 +020066 def __init__(self, controller=RemoteController, monitor=False,
peusterm72f09882018-05-15 17:10:27 +020067 enable_learning=False,
68 # learning switch behavior of the default ovs switches icw Ryu
69 # controller can be turned off/on, needed for E-LAN
70 # functionality
peusterma4d84792016-03-25 12:27:07 +010071 dc_emulation_max_cpu=1.0, # fraction of overall CPU time for emulation
72 dc_emulation_max_mem=512, # emulation max mem in MB
73 **kwargs):
peusterm42f08be2016-03-10 21:56:34 +010074 """
peusterm5877ea22016-05-11 13:44:59 +020075 Create an extended version of a Containernet network
peusterm42f08be2016-03-10 21:56:34 +010076 :param dc_emulation_max_cpu: max. CPU time used by containers in data centers
77 :param kwargs: path through for Mininet parameters
78 :return:
79 """
peusterm8b04b532016-07-19 16:55:38 +020080 # members
peustermcbcd4c22015-12-28 11:33:42 +010081 self.dcs = {}
peusterm8b04b532016-07-19 16:55:38 +020082 self.ryu_process = None
peusterm72f09882018-05-15 17:10:27 +020083 # list of deployed nsds.E_Lines and E_LANs (uploaded from the dummy
84 # gatekeeper)
stevenvanrossembecc7c52016-11-07 05:52:01 +010085 self.deployed_nsds = []
86 self.deployed_elines = []
87 self.deployed_elans = []
88 self.installed_chains = []
89
peusterm8b04b532016-07-19 16:55:38 +020090 # always cleanup environment before we start the emulator
stevenvanrossem89706802016-07-19 02:54:45 +020091 self.killRyu()
peusterm8b04b532016-07-19 16:55:38 +020092 cleanup()
stevenvanrossem89706802016-07-19 02:54:45 +020093
peusterm293cbc32016-01-13 17:05:28 +010094 # call original Docker.__init__ and setup default controller
peusterm5877ea22016-05-11 13:44:59 +020095 Containernet.__init__(
stevenvanrossem7cd3c252016-05-11 22:55:15 +020096 self, switch=OVSKernelSwitch, controller=controller, **kwargs)
stevenvanrossemc5a536a2016-02-16 14:52:39 +010097
stevenvanrossemc3a344f2016-11-04 19:34:47 +010098 # default switch configuration
99 enable_ryu_learning = False
peusterm72f09882018-05-15 17:10:27 +0200100 if enable_learning:
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100101 self.failMode = 'standalone'
102 enable_ryu_learning = True
103 else:
104 self.failMode = 'secure'
105
peustermde14f332016-03-15 16:14:21 +0100106 # Ryu management
peustermde14f332016-03-15 16:14:21 +0100107 if controller == RemoteController:
108 # start Ryu controller
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100109 self.startRyu(learning_switch=enable_ryu_learning)
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100110
peustermde14f332016-03-15 16:14:21 +0100111 # add the specified controller
peustermef6629e2016-03-14 17:21:56 +0100112 self.addController('c0', controller=controller)
113
114 # graph of the complete DC network
stevenvanrossemc1149022016-04-11 01:16:44 +0200115 self.DCNetwork_graph = nx.MultiDiGraph()
peustermef6629e2016-03-14 17:21:56 +0100116
stevenvanrossem461941c2016-05-10 11:41:29 +0200117 # initialize pool of vlan tags to setup the SDN paths
peusterm54ebd582017-07-19 12:11:55 -0700118 self.vlans = range(1, 4095)[::-1]
stevenvanrossem461941c2016-05-10 11:41:29 +0200119
stevenvanrossem27b6d952016-05-10 16:37:57 +0200120 # link to Ryu REST_API
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200121 ryu_ip = 'localhost'
stevenvanrossem27b6d952016-05-10 16:37:57 +0200122 ryu_port = '8080'
123 self.ryu_REST_api = 'http://{0}:{1}'.format(ryu_ip, ryu_port)
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200124 self.RyuSession = requests.Session()
stevenvanrossem27b6d952016-05-10 16:37:57 +0200125
peustermef6629e2016-03-14 17:21:56 +0100126 # monitoring agent
stevenvanrossem60670da2016-04-15 15:31:28 +0200127 if monitor:
128 self.monitor_agent = DCNetworkMonitor(self)
129 else:
130 self.monitor_agent = None
peustermef6629e2016-03-14 17:21:56 +0100131
peusterm42f08be2016-03-10 21:56:34 +0100132 # initialize resource model registrar
peusterma4d84792016-03-25 12:27:07 +0100133 self.rm_registrar = ResourceModelRegistrar(
134 dc_emulation_max_cpu, dc_emulation_max_mem)
stevenvanrossemb3f34172016-11-16 23:30:57 +0100135 self.cpu_period = CPU_PERIOD
peustermcbcd4c22015-12-28 11:33:42 +0100136
peusterm60bf8b82016-04-06 14:12:35 +0200137 def addDatacenter(self, label, metadata={}, resource_log_path=None):
peustermcbcd4c22015-12-28 11:33:42 +0100138 """
139 Create and add a logical cloud data center to the network.
140 """
peusterma47db032016-02-04 14:55:29 +0100141 if label in self.dcs:
142 raise Exception("Data center label already exists: %s" % label)
peusterm72f09882018-05-15 17:10:27 +0200143 dc = Datacenter(label, metadata=metadata,
144 resource_log_path=resource_log_path)
peustermcbcd4c22015-12-28 11:33:42 +0100145 dc.net = self # set reference to network
peusterma47db032016-02-04 14:55:29 +0100146 self.dcs[label] = dc
peustermcbcd4c22015-12-28 11:33:42 +0100147 dc.create() # finally create the data center in our Mininet instance
peustermf9a817d2016-07-18 09:06:04 +0200148 LOG.info("added data center: %s" % label)
peustermcbcd4c22015-12-28 11:33:42 +0100149 return dc
150
peusterme6092692016-01-11 16:32:58 +0100151 def addLink(self, node1, node2, **params):
peusterm5b844a12016-01-11 15:58:15 +0100152 """
153 Able to handle Datacenter objects as link
154 end points.
155 """
peustermcbcd4c22015-12-28 11:33:42 +0100156 assert node1 is not None
157 assert node2 is not None
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100158
peustermcbcd4c22015-12-28 11:33:42 +0100159 # ensure type of node1
peusterm72f09882018-05-15 17:10:27 +0200160 if isinstance(node1, basestring):
peustermcbcd4c22015-12-28 11:33:42 +0100161 if node1 in self.dcs:
162 node1 = self.dcs[node1].switch
peusterm72f09882018-05-15 17:10:27 +0200163 if isinstance(node1, Datacenter):
peustermcbcd4c22015-12-28 11:33:42 +0100164 node1 = node1.switch
165 # ensure type of node2
peusterm72f09882018-05-15 17:10:27 +0200166 if isinstance(node2, basestring):
peustermcbcd4c22015-12-28 11:33:42 +0100167 if node2 in self.dcs:
168 node2 = self.dcs[node2].switch
peusterm72f09882018-05-15 17:10:27 +0200169 if isinstance(node2, Datacenter):
peustermcbcd4c22015-12-28 11:33:42 +0100170 node2 = node2.switch
peustermc3b977e2016-01-12 10:09:35 +0100171 # try to give containers a default IP
peusterm72f09882018-05-15 17:10:27 +0200172 if isinstance(node1, Docker):
peustermea8db832016-03-08 10:25:58 +0100173 if "params1" not in params:
peustermc3b977e2016-01-12 10:09:35 +0100174 params["params1"] = {}
peustermea8db832016-03-08 10:25:58 +0100175 if "ip" not in params["params1"]:
peustermc3b977e2016-01-12 10:09:35 +0100176 params["params1"]["ip"] = self.getNextIp()
peusterm72f09882018-05-15 17:10:27 +0200177 if isinstance(node2, Docker):
peustermea8db832016-03-08 10:25:58 +0100178 if "params2" not in params:
peustermc3b977e2016-01-12 10:09:35 +0100179 params["params2"] = {}
peustermea8db832016-03-08 10:25:58 +0100180 if "ip" not in params["params2"]:
peustermc3b977e2016-01-12 10:09:35 +0100181 params["params2"]["ip"] = self.getNextIp()
peustermea8db832016-03-08 10:25:58 +0100182 # ensure that we allow TCLinks between data centers
183 # TODO this is not optimal, we use cls=Link for containers and TCLink for data centers
peusterm72f09882018-05-15 17:10:27 +0200184 # see Containernet issue:
185 # https://github.com/mpeuster/containernet/issues/3
peustermea8db832016-03-08 10:25:58 +0100186 if "cls" not in params:
187 params["cls"] = TCLink
peustermc3b977e2016-01-12 10:09:35 +0100188
peusterm5877ea22016-05-11 13:44:59 +0200189 link = Containernet.addLink(self, node1, node2, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100190
stevenvanrossemc1149022016-04-11 01:16:44 +0200191 # try to give container interfaces a default id
192 node1_port_id = node1.ports[link.intf1]
193 if isinstance(node1, Docker):
194 if "id" in params["params1"]:
195 node1_port_id = params["params1"]["id"]
stevenvanrossem5b376412016-05-04 15:34:49 +0200196 node1_port_name = link.intf1.name
stevenvanrossemc1149022016-04-11 01:16:44 +0200197
198 node2_port_id = node2.ports[link.intf2]
199 if isinstance(node2, Docker):
200 if "id" in params["params2"]:
201 node2_port_id = params["params2"]["id"]
stevenvanrossem5b376412016-05-04 15:34:49 +0200202 node2_port_name = link.intf2.name
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200203
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100204 # add edge and assigned port number to graph in both directions between node1 and node2
stevenvanrossemc1149022016-04-11 01:16:44 +0200205 # port_id: id given in descriptor (if available, otherwise same as port)
peusterm5877ea22016-05-11 13:44:59 +0200206 # port: portnumber assigned by Containernet
stevenvanrossemc1149022016-04-11 01:16:44 +0200207
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200208 attr_dict = {}
209 # possible weight metrics allowed by TClink class:
210 weight_metrics = ['bw', 'delay', 'jitter', 'loss']
211 edge_attributes = [p for p in params if p in weight_metrics]
212 for attr in edge_attributes:
213 # if delay: strip ms (need number as weight in graph)
joka27edb4f2017-01-17 12:40:59 +0100214 match = re.search('([0-9]*\.?[0-9]+)', str(params[attr]))
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200215 if match:
216 attr_number = match.group(1)
217 else:
218 attr_number = None
219 attr_dict[attr] = attr_number
220
stevenvanrossem5b376412016-05-04 15:34:49 +0200221 attr_dict2 = {'src_port_id': node1_port_id, 'src_port_nr': node1.ports[link.intf1],
222 'src_port_name': node1_port_name,
peusterm72f09882018-05-15 17:10:27 +0200223 'dst_port_id': node2_port_id, 'dst_port_nr': node2.ports[link.intf2],
stevenvanrossem5b376412016-05-04 15:34:49 +0200224 'dst_port_name': node2_port_name}
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200225 attr_dict2.update(attr_dict)
peusterm72f09882018-05-15 17:10:27 +0200226 self.DCNetwork_graph.add_edge(
227 node1.name, node2.name, attr_dict=attr_dict2)
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200228
stevenvanrossem5b376412016-05-04 15:34:49 +0200229 attr_dict2 = {'src_port_id': node2_port_id, 'src_port_nr': node2.ports[link.intf2],
230 'src_port_name': node2_port_name,
peusterm72f09882018-05-15 17:10:27 +0200231 'dst_port_id': node1_port_id, 'dst_port_nr': node1.ports[link.intf1],
stevenvanrossem5b376412016-05-04 15:34:49 +0200232 'dst_port_name': node1_port_name}
stevenvanrossem6b1d9b92016-05-02 13:10:40 +0200233 attr_dict2.update(attr_dict)
peusterm72f09882018-05-15 17:10:27 +0200234 self.DCNetwork_graph.add_edge(
235 node2.name, node1.name, attr_dict=attr_dict2)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100236
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100237 LOG.debug("addLink: n1={0} intf1={1} -- n2={2} intf2={3}".format(
peusterm72f09882018-05-15 17:10:27 +0200238 str(node1), node1_port_name, str(node2), node2_port_name))
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100239
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100240 return link
peustermcbcd4c22015-12-28 11:33:42 +0100241
stevenvanrossem00e65b92017-04-18 16:57:40 +0200242 def removeLink(self, link=None, node1=None, node2=None):
243 """
244 Remove the link from the Containernet and the networkx graph
245 """
peusterm92a37cd2017-05-17 09:51:05 +0200246 if link is not None:
247 node1 = link.intf1.node
248 node2 = link.intf2.node
249 assert node1 is not None
250 assert node2 is not None
stevenvanrossem00e65b92017-04-18 16:57:40 +0200251 Containernet.removeLink(self, link=link, node1=node1, node2=node2)
peusterm92a37cd2017-05-17 09:51:05 +0200252 # TODO we might decrease the loglevel to debug:
253 try:
254 self.DCNetwork_graph.remove_edge(node2.name, node1.name)
peusterm72f09882018-05-15 17:10:27 +0200255 except BaseException:
256 LOG.warning("%s, %s not found in DCNetwork_graph." %
257 ((node2.name, node1.name)))
peusterm92a37cd2017-05-17 09:51:05 +0200258 try:
259 self.DCNetwork_graph.remove_edge(node1.name, node2.name)
peusterm72f09882018-05-15 17:10:27 +0200260 except BaseException:
261 LOG.warning("%s, %s not found in DCNetwork_graph." %
262 ((node1.name, node2.name)))
stevenvanrossem00e65b92017-04-18 16:57:40 +0200263
peusterm72f09882018-05-15 17:10:27 +0200264 def addDocker(self, label, **params):
peusterm5b844a12016-01-11 15:58:15 +0100265 """
peusterm293cbc32016-01-13 17:05:28 +0100266 Wrapper for addDocker method to use custom container class.
peusterm5b844a12016-01-11 15:58:15 +0100267 """
stevenvanrossemf3712012017-05-04 00:01:52 +0200268 self.DCNetwork_graph.add_node(label, type=params.get('type', 'docker'))
peusterm72f09882018-05-15 17:10:27 +0200269 return Containernet.addDocker(
270 self, label, cls=EmulatorCompute, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100271
peusterm72f09882018-05-15 17:10:27 +0200272 def removeDocker(self, label, **params):
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100273 """
274 Wrapper for removeDocker method to update graph.
275 """
stevenvanrossem8fbf9782016-02-17 11:40:23 +0100276 self.DCNetwork_graph.remove_node(label)
peusterm5877ea22016-05-11 13:44:59 +0200277 return Containernet.removeDocker(self, label, **params)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100278
stevenvanrossemf3712012017-05-04 00:01:52 +0200279 def addExtSAP(self, sap_name, sap_ip, **params):
280 """
281 Wrapper for addExtSAP method to store SAP also in graph.
282 """
283 # make sure that 'type' is set
peusterm72f09882018-05-15 17:10:27 +0200284 params['type'] = params.get('type', 'sap_ext')
stevenvanrossemf3712012017-05-04 00:01:52 +0200285 self.DCNetwork_graph.add_node(sap_name, type=params['type'])
stevenvanrossemf3712012017-05-04 00:01:52 +0200286 return Containernet.addExtSAP(self, sap_name, sap_ip, **params)
287
288 def removeExtSAP(self, sap_name, **params):
289 """
290 Wrapper for removeExtSAP method to remove SAP also from graph.
291 """
292 self.DCNetwork_graph.remove_node(sap_name)
293 return Containernet.removeExtSAP(self, sap_name)
294
peusterm72f09882018-05-15 17:10:27 +0200295 def addSwitch(self, name, add_to_graph=True, **params):
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100296 """
297 Wrapper for addSwitch method to store switch also in graph.
298 """
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100299
300 # add this switch to the global topology overview
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100301 if add_to_graph:
peusterm72f09882018-05-15 17:10:27 +0200302 self.DCNetwork_graph.add_node(
303 name, type=params.get('type', 'switch'))
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100304
305 # set the learning switch behavior
peusterm72f09882018-05-15 17:10:27 +0200306 if 'failMode' in params:
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100307 failMode = params['failMode']
peusterm72f09882018-05-15 17:10:27 +0200308 else:
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100309 failMode = self.failMode
310
peusterm72f09882018-05-15 17:10:27 +0200311 s = Containernet.addSwitch(
312 self, name, protocols='OpenFlow10,OpenFlow12,OpenFlow13', failMode=failMode, **params)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100313
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100314 return s
peustermc3b977e2016-01-12 10:09:35 +0100315
peustermbd44f4a2016-01-13 14:53:30 +0100316 def getAllContainers(self):
317 """
318 Returns a list with all containers within all data centers.
319 """
320 all_containers = []
321 for dc in self.dcs.itervalues():
322 all_containers += dc.listCompute()
323 return all_containers
324
peustermcbcd4c22015-12-28 11:33:42 +0100325 def start(self):
326 # start
327 for dc in self.dcs.itervalues():
328 dc.start()
peusterm5877ea22016-05-11 13:44:59 +0200329 Containernet.start(self)
peustermcbcd4c22015-12-28 11:33:42 +0100330
331 def stop(self):
stevenvanrossem60670da2016-04-15 15:31:28 +0200332
stevenvanrossemc6abf132016-04-14 11:15:58 +0200333 # stop the monitor agent
stevenvanrossem60670da2016-04-15 15:31:28 +0200334 if self.monitor_agent is not None:
335 self.monitor_agent.stop()
peustermcbcd4c22015-12-28 11:33:42 +0100336
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200337 # stop emulator net
peusterm5877ea22016-05-11 13:44:59 +0200338 Containernet.stop(self)
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200339
340 # stop Ryu controller
peusterm8b04b532016-07-19 16:55:38 +0200341 self.killRyu()
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200342
peustermcbcd4c22015-12-28 11:33:42 +0100343 def CLI(self):
peusterm293cbc32016-01-13 17:05:28 +0100344 CLI(self)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100345
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100346 def setLAN(self, vnf_list):
347 """
348 setup an E-LAN network by assigning the same VLAN tag to each DC interface of the VNFs in the E-LAN
349
350 :param vnf_list: names of the VNFs in this E-LAN [{name:,interface:},...]
351 :return:
352 """
353 src_sw = None
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100354 src_sw_inport_name = None
355
356 # get a vlan tag for this E-LAN
357 vlan = self.vlans.pop()
358
359 for vnf in vnf_list:
360 vnf_src_name = vnf['name']
361 vnf_src_interface = vnf['interface']
362
363 # check if port is specified (vnf:port)
364 if vnf_src_interface is None:
365 # take first interface by default
366 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
367 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
368 vnf_src_interface = link_dict[0]['src_port_id']
369
370 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
371 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
372 for link in link_dict:
373 if (link_dict[link]['src_port_id'] == vnf_src_interface or
peusterm72f09882018-05-15 17:10:27 +0200374 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 +0100375 # found the right link and connected switch
376 src_sw = connected_sw
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100377 src_sw_inport_name = link_dict[link]['dst_port_name']
378 break
379
380 # set the tag on the dc switch interface
peusterm72f09882018-05-15 17:10:27 +0200381 LOG.debug('set E-LAN: vnf name: {0} interface: {1} tag: {2}'.format(
382 vnf_src_name, vnf_src_interface, vlan))
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100383 switch_node = self.getNodeByName(src_sw)
384 self._set_vlan_tag(switch_node, src_sw_inport_name, vlan)
385
stevenvanrossembecc7c52016-11-07 05:52:01 +0100386 def _addMonitorFlow(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None,
peusterm72f09882018-05-15 17:10:27 +0200387 tag=None, **kwargs):
stevenvanrossembf1754e2016-11-17 10:20:52 +0100388 """
389 Add a monitoring flow entry that adds a special flowentry/counter at the begin or end of a chain.
390 So this monitoring flowrule exists on top of a previously defined chain rule and uses the same vlan tag/routing.
391 :param vnf_src_name:
392 :param vnf_dst_name:
393 :param vnf_src_interface:
394 :param vnf_dst_interface:
395 :param tag: vlan tag to be used for this chain (same tag as existing chain)
396 :param monitor_placement: 'tx' or 'rx' indicating to place the extra flowentry resp. at the beginning or end of the chain
397 :return:
398 """
stevenvanrossembecc7c52016-11-07 05:52:01 +0100399
400 src_sw = None
401 src_sw_inport_nr = 0
402 src_sw_inport_name = None
403 dst_sw = None
404 dst_sw_outport_nr = 0
405 dst_sw_outport_name = None
406
407 LOG.debug("call AddMonitorFlow vnf_src_name=%r, vnf_src_interface=%r, vnf_dst_name=%r, vnf_dst_interface=%r",
408 vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface)
409
peusterm72f09882018-05-15 17:10:27 +0200410 # check if port is specified (vnf:port)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100411 if vnf_src_interface is None:
412 # take first interface by default
413 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
414 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
415 vnf_src_interface = link_dict[0]['src_port_id']
416
417 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
418 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
419 for link in link_dict:
420 if (link_dict[link]['src_port_id'] == vnf_src_interface or
421 link_dict[link]['src_port_name'] == vnf_src_interface): # Fix: we might also get interface names, e.g, from a son-emu-cli call
422 # found the right link and connected switch
423 src_sw = connected_sw
424 src_sw_inport_nr = link_dict[link]['dst_port_nr']
425 src_sw_inport_name = link_dict[link]['dst_port_name']
426 break
427
428 if vnf_dst_interface is None:
429 # take first interface by default
430 connected_sw = self.DCNetwork_graph.neighbors(vnf_dst_name)[0]
431 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
432 vnf_dst_interface = link_dict[0]['dst_port_id']
433
434 vnf_dst_name = vnf_dst_name.split(':')[0]
435 for connected_sw in self.DCNetwork_graph.neighbors(vnf_dst_name):
436 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
437 for link in link_dict:
438 if link_dict[link]['dst_port_id'] == vnf_dst_interface or \
439 link_dict[link]['dst_port_name'] == vnf_dst_interface: # Fix: we might also get interface names, e.g, from a son-emu-cli call
440 # found the right link and connected switch
441 dst_sw = connected_sw
442 dst_sw_outport_nr = link_dict[link]['src_port_nr']
443 dst_sw_outport_name = link_dict[link]['src_port_name']
444 break
445
446 if not tag >= 0:
447 LOG.exception('tag not valid: {0}'.format(tag))
448
449 # get shortest path
450 try:
451 # returns the first found shortest path
452 # if all shortest paths are wanted, use: all_shortest_paths
peusterm72f09882018-05-15 17:10:27 +0200453 path = nx.shortest_path(
454 self.DCNetwork_graph, src_sw, dst_sw, weight=kwargs.get('weight'))
455 except BaseException:
stevenvanrossembecc7c52016-11-07 05:52:01 +0100456 LOG.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
457 vnf_src_name, vnf_dst_name, src_sw, dst_sw))
458 LOG.debug("Graph nodes: %r" % self.DCNetwork_graph.nodes())
459 LOG.debug("Graph edges: %r" % self.DCNetwork_graph.edges())
460 for e, v in self.DCNetwork_graph.edges():
461 LOG.debug("%r" % self.DCNetwork_graph[e][v])
peusterm72f09882018-05-15 17:10:27 +0200462 return "No path could be found between {0} and {1}".format(
463 vnf_src_name, vnf_dst_name)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100464
peusterm37911562018-10-18 15:03:55 +0200465 LOG.debug("Creating path between {0} and {1}: {2}".format(
peusterm72f09882018-05-15 17:10:27 +0200466 vnf_src_name, vnf_dst_name, path))
stevenvanrossembecc7c52016-11-07 05:52:01 +0100467
468 current_hop = src_sw
469 switch_inport_nr = src_sw_inport_nr
470
471 cmd = kwargs.get('cmd')
472
peusterm72f09882018-05-15 17:10:27 +0200473 # iterate through the path to install the flow-entries
474 for i in range(0, len(path)):
stevenvanrossembecc7c52016-11-07 05:52:01 +0100475 current_node = self.getNodeByName(current_hop)
476
peusterm72f09882018-05-15 17:10:27 +0200477 if path.index(current_hop) < len(path) - 1:
478 next_hop = path[path.index(current_hop) + 1]
stevenvanrossembecc7c52016-11-07 05:52:01 +0100479 else:
peusterm72f09882018-05-15 17:10:27 +0200480 # last switch reached
stevenvanrossembecc7c52016-11-07 05:52:01 +0100481 next_hop = vnf_dst_name
482
483 next_node = self.getNodeByName(next_hop)
484
485 if next_hop == vnf_dst_name:
486 switch_outport_nr = dst_sw_outport_nr
peusterm37911562018-10-18 15:03:55 +0200487 LOG.debug("end node reached: {0}".format(vnf_dst_name))
peusterm72f09882018-05-15 17:10:27 +0200488 elif not isinstance(next_node, OVSSwitch):
stevenvanrossembecc7c52016-11-07 05:52:01 +0100489 LOG.info("Next node: {0} is not a switch".format(next_hop))
490 return "Next node: {0} is not a switch".format(next_hop)
491 else:
492 # take first link between switches by default
493 index_edge_out = 0
494 switch_outport_nr = self.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100495
peusterm72f09882018-05-15 17:10:27 +0200496 # set of entry via ovs-ofctl
497 if isinstance(current_node, OVSSwitch):
stevenvanrossembecc7c52016-11-07 05:52:01 +0100498 kwargs['vlan'] = tag
499 kwargs['path'] = path
500 kwargs['current_hop'] = current_hop
501 kwargs['switch_inport_name'] = src_sw_inport_name
502 kwargs['switch_outport_name'] = dst_sw_outport_name
503 kwargs['skip_vlan_tag'] = True
stevenvanrossem263eee52017-02-08 01:04:36 +0100504 kwargs['pathindex'] = i
stevenvanrossembecc7c52016-11-07 05:52:01 +0100505
stevenvanrossem4fac2af2016-12-22 01:26:02 +0100506 monitor_placement = kwargs.get('monitor_placement').strip()
stevenvanrossembecc7c52016-11-07 05:52:01 +0100507 # put monitor flow at the dst switch
508 insert_flow = False
peusterm72f09882018-05-15 17:10:27 +0200509 # first node:
510 if monitor_placement == 'tx' and path.index(current_hop) == 0:
stevenvanrossembecc7c52016-11-07 05:52:01 +0100511 insert_flow = True
512 # put monitoring flow at the src switch
peusterm72f09882018-05-15 17:10:27 +0200513 # last node:
514 elif monitor_placement == 'rx' and path.index(current_hop) == len(path) - 1:
stevenvanrossembecc7c52016-11-07 05:52:01 +0100515 insert_flow = True
stevenvanrossem4fac2af2016-12-22 01:26:02 +0100516 elif monitor_placement not in ['rx', 'tx']:
peusterm72f09882018-05-15 17:10:27 +0200517 LOG.exception(
518 'invalid monitor command: {0}'.format(monitor_placement))
stevenvanrossembecc7c52016-11-07 05:52:01 +0100519
520 if self.controller == RemoteController and insert_flow:
peusterm72f09882018-05-15 17:10:27 +0200521 # set flow entry via ryu rest api
522 self._set_flow_entry_ryu_rest(
523 current_node, switch_inport_nr, switch_outport_nr, **kwargs)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100524 break
525 elif insert_flow:
peusterm72f09882018-05-15 17:10:27 +0200526 # set flow entry via ovs-ofctl
527 self._set_flow_entry_dpctl(
528 current_node, switch_inport_nr, switch_outport_nr, **kwargs)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100529 break
530
531 # take first link between switches by default
peusterm72f09882018-05-15 17:10:27 +0200532 if isinstance(next_node, OVSSwitch):
stevenvanrossembecc7c52016-11-07 05:52:01 +0100533 switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
534 current_hop = next_hop
535
peusterm72f09882018-05-15 17:10:27 +0200536 return "path {2} between {0} and {1}".format(
537 vnf_src_name, vnf_dst_name, cmd)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100538
peusterm72f09882018-05-15 17:10:27 +0200539 def setChain(self, vnf_src_name, vnf_dst_name,
540 vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200541 """
542 Chain 2 vnf interfaces together by installing the flowrules in the switches along their path.
543 Currently the path is found using the default networkx shortest path function.
544 Each chain gets a unique vlan id , so different chains wil not interfere.
545
546 :param vnf_src_name: vnf name (string)
547 :param vnf_dst_name: vnf name (string)
548 :param vnf_src_interface: source interface name (string)
549 :param vnf_dst_interface: destination interface name (string)
550 :param cmd: 'add-flow' (default) to add a chain, 'del-flows' to remove a chain
551 :param cookie: cookie for the installed flowrules (can be used later as identifier for a set of installed chains)
552 :param match: custom match entry to be added to the flowrules (default: only in_port and vlan tag)
553 :param priority: custom flowrule priority
stevenvanrossembf1754e2016-11-17 10:20:52 +0100554 :param monitor: boolean to indicate whether this chain is a monitoring chain
555 :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 +0100556 :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 +0100557 :param path: custom path between the two VNFs (list of switches)
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200558 :return: output log string
559 """
stevenvanrossembecc7c52016-11-07 05:52:01 +0100560
561 # special procedure for monitoring flows
562 if kwargs.get('monitor'):
563
564 # check if chain already exists
565 found_chains = [chain_dict for chain_dict in self.installed_chains if
peusterm72f09882018-05-15 17:10:27 +0200566 (chain_dict['vnf_src_name'] == vnf_src_name and
567 chain_dict['vnf_src_interface'] == vnf_src_interface and
568 chain_dict['vnf_dst_name'] == vnf_dst_name and
569 chain_dict['vnf_dst_interface'] == vnf_dst_interface)]
stevenvanrossembecc7c52016-11-07 05:52:01 +0100570
571 if len(found_chains) > 0:
572 # this chain exists, so need an extra monitoring flow
573 # assume only 1 chain per vnf/interface pair
574 LOG.debug('*** installing monitoring chain on top of pre-defined chain from {0}:{1} -> {2}:{3}'.
peusterm72f09882018-05-15 17:10:27 +0200575 format(vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface))
stevenvanrossembecc7c52016-11-07 05:52:01 +0100576 tag = found_chains[0]['tag']
577 ret = self._addMonitorFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface,
peusterm72f09882018-05-15 17:10:27 +0200578 tag=tag, table_id=0, **kwargs)
stevenvanrossembecc7c52016-11-07 05:52:01 +0100579 return ret
580 else:
581 # no chain existing (or E-LAN) -> install normal chain
stevenvanrossem4fac2af2016-12-22 01:26:02 +0100582 LOG.warning('*** installing monitoring chain without pre-defined NSD chain from {0}:{1} -> {2}:{3}'.
stevenvanrossembecc7c52016-11-07 05:52:01 +0100583 format(vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface))
584 pass
585
splietker7b38ee12017-06-28 17:24:01 +0200586 cmd = kwargs.get('cmd', 'add-flow')
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100587 if cmd == 'add-flow' or cmd == 'del-flows':
peusterm72f09882018-05-15 17:10:27 +0200588 ret = self._chainAddFlow(
589 vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface, **kwargs)
stevenvanrossem461941c2016-05-10 11:41:29 +0200590 if kwargs.get('bidirectional'):
jokac304ad32017-01-09 10:58:23 +0100591 if kwargs.get('path') is not None:
592 kwargs['path'] = list(reversed(kwargs.get('path')))
peusterm72f09882018-05-15 17:10:27 +0200593 ret = ret + '\n' + \
594 self._chainAddFlow(
595 vnf_dst_name, vnf_src_name, vnf_dst_interface, vnf_src_interface, **kwargs)
stevenvanrossem9315da42016-04-11 12:10:06 +0200596
stevenvanrossem461941c2016-05-10 11:41:29 +0200597 else:
stevenvanrossem81955a52016-05-12 14:34:12 +0200598 ret = "Command unknown"
599
600 return ret
stevenvanrossem461941c2016-05-10 11:41:29 +0200601
peusterm72f09882018-05-15 17:10:27 +0200602 def _chainAddFlow(self, vnf_src_name, vnf_dst_name,
603 vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
stevenvanrossem461941c2016-05-10 11:41:29 +0200604
peusterm53d3c142016-07-18 10:10:11 +0200605 src_sw = None
peusterm53d3c142016-07-18 10:10:11 +0200606 src_sw_inport_nr = 0
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100607 src_sw_inport_name = None
608 dst_sw = None
peusterm53d3c142016-07-18 10:10:11 +0200609 dst_sw_outport_nr = 0
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100610 dst_sw_outport_name = None
peusterm53d3c142016-07-18 10:10:11 +0200611
612 LOG.debug("call chainAddFlow vnf_src_name=%r, vnf_src_interface=%r, vnf_dst_name=%r, vnf_dst_interface=%r",
613 vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface)
614
peusterm72f09882018-05-15 17:10:27 +0200615 # check if port is specified (vnf:port)
stevenvanrossemed711fd2016-04-11 16:59:29 +0200616 if vnf_src_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200617 # take first interface by default
618 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
619 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200620 vnf_src_interface = link_dict[0]['src_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200621
stevenvanrossem9315da42016-04-11 12:10:06 +0200622 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
623 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
624 for link in link_dict:
peusterm53d3c142016-07-18 10:10:11 +0200625 if (link_dict[link]['src_port_id'] == vnf_src_interface or
626 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 +0200627 # found the right link and connected switch
stevenvanrossem9315da42016-04-11 12:10:06 +0200628 src_sw = connected_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200629 src_sw_inport_nr = link_dict[link]['dst_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100630 src_sw_inport_name = link_dict[link]['dst_port_name']
stevenvanrossem9315da42016-04-11 12:10:06 +0200631 break
632
stevenvanrossemed711fd2016-04-11 16:59:29 +0200633 if vnf_dst_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200634 # take first interface by default
635 connected_sw = self.DCNetwork_graph.neighbors(vnf_dst_name)[0]
636 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
stevenvanrossemed711fd2016-04-11 16:59:29 +0200637 vnf_dst_interface = link_dict[0]['dst_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200638
639 vnf_dst_name = vnf_dst_name.split(':')[0]
640 for connected_sw in self.DCNetwork_graph.neighbors(vnf_dst_name):
641 link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
642 for link in link_dict:
peusterm53d3c142016-07-18 10:10:11 +0200643 if link_dict[link]['dst_port_id'] == vnf_dst_interface or \
644 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 +0200645 # found the right link and connected switch
646 dst_sw = connected_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200647 dst_sw_outport_nr = link_dict[link]['src_port_nr']
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100648 dst_sw_outport_name = link_dict[link]['src_port_name']
stevenvanrossem9315da42016-04-11 12:10:06 +0200649 break
650
jokac304ad32017-01-09 10:58:23 +0100651 path = kwargs.get('path')
652 if path is None:
653 # get shortest path
654 try:
655 # returns the first found shortest path
656 # if all shortest paths are wanted, use: all_shortest_paths
peusterm72f09882018-05-15 17:10:27 +0200657 path = nx.shortest_path(
658 self.DCNetwork_graph, src_sw, dst_sw, weight=kwargs.get('weight'))
659 except BaseException:
jokac304ad32017-01-09 10:58:23 +0100660 LOG.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
661 vnf_src_name, vnf_dst_name, src_sw, dst_sw))
662 LOG.debug("Graph nodes: %r" % self.DCNetwork_graph.nodes())
663 LOG.debug("Graph edges: %r" % self.DCNetwork_graph.edges())
664 for e, v in self.DCNetwork_graph.edges():
665 LOG.debug("%r" % self.DCNetwork_graph[e][v])
peusterm72f09882018-05-15 17:10:27 +0200666 return "No path could be found between {0} and {1}".format(
667 vnf_src_name, vnf_dst_name)
stevenvanrossem9315da42016-04-11 12:10:06 +0200668
peusterm37911562018-10-18 15:03:55 +0200669 LOG.debug("Creating path between {0} and {1}: {2}".format(
peusterm72f09882018-05-15 17:10:27 +0200670 vnf_src_name, vnf_dst_name, path))
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100671
stevenvanrossem9315da42016-04-11 12:10:06 +0200672 current_hop = src_sw
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200673 switch_inport_nr = src_sw_inport_nr
stevenvanrossem9315da42016-04-11 12:10:06 +0200674
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100675 # choose free vlan
stevenvanrossem27b6d952016-05-10 16:37:57 +0200676 cmd = kwargs.get('cmd')
677 vlan = None
stevenvanrossem263eee52017-02-08 01:04:36 +0100678 if cmd == 'add-flow':
stevenvanrossembecc7c52016-11-07 05:52:01 +0100679 if kwargs.get('tag'):
680 # use pre-defined tag
681 vlan = kwargs.get('tag')
682 else:
683 vlan = self.vlans.pop()
stevenvanrossem461941c2016-05-10 11:41:29 +0200684
stevenvanrossembecc7c52016-11-07 05:52:01 +0100685 # store the used vlan tag to identify this chain
686 if not kwargs.get('monitor'):
687 chain_dict = {}
688 chain_dict['vnf_src_name'] = vnf_src_name
689 chain_dict['vnf_dst_name'] = vnf_dst_name
690 chain_dict['vnf_src_interface'] = vnf_src_interface
691 chain_dict['vnf_dst_interface'] = vnf_dst_interface
692 chain_dict['tag'] = vlan
693 self.installed_chains.append(chain_dict)
694
peusterm72f09882018-05-15 17:10:27 +0200695 # iterate through the path to install the flow-entries
696 for i in range(0, len(path)):
stevenvanrossem9315da42016-04-11 12:10:06 +0200697 current_node = self.getNodeByName(current_hop)
stevenvanrossem461941c2016-05-10 11:41:29 +0200698
jokac304ad32017-01-09 10:58:23 +0100699 if i < len(path) - 1:
700 next_hop = path[i + 1]
stevenvanrossem9315da42016-04-11 12:10:06 +0200701 else:
jokac304ad32017-01-09 10:58:23 +0100702 # last switch reached
stevenvanrossem9315da42016-04-11 12:10:06 +0200703 next_hop = vnf_dst_name
704
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100705 next_node = self.getNodeByName(next_hop)
706
707 if next_hop == vnf_dst_name:
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200708 switch_outport_nr = dst_sw_outport_nr
peusterm37911562018-10-18 15:03:55 +0200709 LOG.debug("end node reached: {0}".format(vnf_dst_name))
peusterm72f09882018-05-15 17:10:27 +0200710 elif not isinstance(next_node, OVSSwitch):
peustermf9a817d2016-07-18 09:06:04 +0200711 LOG.info("Next node: {0} is not a switch".format(next_hop))
stevenvanrossemeefea6c2016-02-17 12:03:26 +0100712 return "Next node: {0} is not a switch".format(next_hop)
stevenvanrossem9315da42016-04-11 12:10:06 +0200713 else:
stevenvanrossemed711fd2016-04-11 16:59:29 +0200714 # take first link between switches by default
stevenvanrossem9315da42016-04-11 12:10:06 +0200715 index_edge_out = 0
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200716 switch_outport_nr = self.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
stevenvanrossem9315da42016-04-11 12:10:06 +0200717
peusterm72f09882018-05-15 17:10:27 +0200718 # set OpenFlow entry
719 if isinstance(current_node, OVSSwitch):
stevenvanrossem461941c2016-05-10 11:41:29 +0200720 kwargs['vlan'] = vlan
721 kwargs['path'] = path
722 kwargs['current_hop'] = current_hop
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100723 kwargs['switch_inport_name'] = src_sw_inport_name
724 kwargs['switch_outport_name'] = dst_sw_outport_name
jokac304ad32017-01-09 10:58:23 +0100725 kwargs['pathindex'] = i
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200726
727 if self.controller == RemoteController:
peusterm72f09882018-05-15 17:10:27 +0200728 # set flow entry via ryu rest api
729 self._set_flow_entry_ryu_rest(
730 current_node, switch_inport_nr, switch_outport_nr, **kwargs)
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200731 else:
peusterm72f09882018-05-15 17:10:27 +0200732 # set flow entry via ovs-ofctl
733 self._set_flow_entry_dpctl(
734 current_node, switch_inport_nr, switch_outport_nr, **kwargs)
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200735
stevenvanrossemed711fd2016-04-11 16:59:29 +0200736 # take first link between switches by default
peusterm72f09882018-05-15 17:10:27 +0200737 if isinstance(next_node, OVSSwitch):
stevenvanrossemedcbeeb2016-05-04 17:28:08 +0200738 switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
stevenvanrossemed711fd2016-04-11 16:59:29 +0200739 current_hop = next_hop
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100740
stevenvanrossemae588012017-06-06 10:33:19 +0200741 flow_options = {
peusterm72f09882018-05-15 17:10:27 +0200742 'priority': kwargs.get('priority', DEFAULT_PRIORITY),
743 'cookie': kwargs.get('cookie', DEFAULT_COOKIE),
744 'vlan': kwargs['vlan'],
745 'path': kwargs['path'],
746 'match_input': kwargs.get('match')
stevenvanrossemae588012017-06-06 10:33:19 +0200747 }
748 flow_options_str = json.dumps(flow_options, indent=1)
peusterm37911562018-10-18 15:03:55 +0200749 LOG.info("Installed flow rule: ({}:{}) -> ({}:{}) with options: {}"
750 .format(vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface, flow_options))
peusterm72f09882018-05-15 17:10:27 +0200751 return "success: {2} between {0} and {1} with options: {3}".format(
752 vnf_src_name, vnf_dst_name, cmd, flow_options_str)
stevenvanrossem27b6d952016-05-10 16:37:57 +0200753
peusterm72f09882018-05-15 17:10:27 +0200754 def _set_flow_entry_ryu_rest(
755 self, node, switch_inport_nr, switch_outport_nr, **kwargs):
stevenvanrossem27b6d952016-05-10 16:37:57 +0200756 match = 'in_port=%s' % switch_inport_nr
757
758 cookie = kwargs.get('cookie')
759 match_input = kwargs.get('match')
760 cmd = kwargs.get('cmd')
761 path = kwargs.get('path')
jokac304ad32017-01-09 10:58:23 +0100762 index = kwargs.get('pathindex')
763
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
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100830
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200831 # output action must come last
832 action = {}
833 action['type'] = 'OUTPUT'
834 action['port'] = switch_outport_nr
835 flow['actions'].append(action)
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200836
stevenvanrossem27b6d952016-05-10 16:37:57 +0200837 elif cmd == 'del-flows':
stevenvanrossem27b6d952016-05-10 16:37:57 +0200838 prefix = 'stats/flowentry/delete'
stevenvanrossem7fce49b2016-05-12 01:34:15 +0200839
stevenvanrossem27b6d952016-05-10 16:37:57 +0200840 if cookie:
stevenvanrossem1ef77022016-05-12 16:36:10 +0200841 # TODO: add cookie_mask as argument
peusterm72f09882018-05-15 17:10:27 +0200842 # need full mask to match complete cookie
843 flow['cookie_mask'] = int('0xffffffffffffffff', 16)
stevenvanrossem27b6d952016-05-10 16:37:57 +0200844
845 action = {}
846 action['type'] = 'OUTPUT'
847 action['port'] = switch_outport_nr
848 flow['actions'].append(action)
849
850 flow['match'] = self._parse_match(match)
851 self.ryu_REST(prefix, data=flow)
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100852
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100853 def _set_vlan_tag(self, node, switch_port, tag):
peusterm72f09882018-05-15 17:10:27 +0200854 node.vsctl('set', 'port {0} tag={1}'.format(switch_port, tag))
855 LOG.debug("set vlan in switch: {0} in_port: {1} vlan tag: {2}".format(
856 node.name, switch_port, tag))
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100857
peusterm72f09882018-05-15 17:10:27 +0200858 def _set_flow_entry_dpctl(
859 self, node, switch_inport_nr, switch_outport_nr, **kwargs):
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100860
stevenvanrossem23c48092016-05-06 17:21:12 +0200861 match = 'in_port=%s' % switch_inport_nr
stevenvanrossem461941c2016-05-10 11:41:29 +0200862
863 cookie = kwargs.get('cookie')
864 match_input = kwargs.get('match')
865 cmd = kwargs.get('cmd')
866 path = kwargs.get('path')
jokac304ad32017-01-09 10:58:23 +0100867 index = kwargs.get('pathindex')
stevenvanrossem461941c2016-05-10 11:41:29 +0200868 vlan = kwargs.get('vlan')
869
stevenvanrossem898a2af2016-05-06 18:28:57 +0200870 s = ','
871 if cookie:
872 cookie = 'cookie=%s' % cookie
873 match = s.join([cookie, match])
stevenvanrossem23c48092016-05-06 17:21:12 +0200874 if match_input:
stevenvanrossem23c48092016-05-06 17:21:12 +0200875 match = s.join([match, match_input])
stevenvanrossem23c48092016-05-06 17:21:12 +0200876 if cmd == 'add-flow':
877 action = 'action=%s' % switch_outport_nr
peusterm72f09882018-05-15 17:10:27 +0200878 if vlan is not None:
879 if index == 0: # first node
880 action = ('action=mod_vlan_vid:%s' % vlan) + \
881 (',output=%s' % switch_outport_nr)
stevenvanrossem461941c2016-05-10 11:41:29 +0200882 match = '-O OpenFlow13 ' + match
jokac304ad32017-01-09 10:58:23 +0100883 elif index == len(path) - 1: # last node
stevenvanrossem461941c2016-05-10 11:41:29 +0200884 match += ',dl_vlan=%s' % vlan
885 action = 'action=strip_vlan,output=%s' % switch_outport_nr
886 else: # middle nodes
887 match += ',dl_vlan=%s' % vlan
stevenvanrossem23c48092016-05-06 17:21:12 +0200888 ofcmd = s.join([match, action])
889 elif cmd == 'del-flows':
890 ofcmd = match
891 else:
892 ofcmd = ''
893
894 node.dpctl(cmd, ofcmd)
peustermf9a817d2016-07-18 09:06:04 +0200895 LOG.info("{3} in switch: {0} in_port: {1} out_port: {2}".format(node.name, switch_inport_nr,
peusterm72f09882018-05-15 17:10:27 +0200896 switch_outport_nr, cmd))
stevenvanrossem23c48092016-05-06 17:21:12 +0200897
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100898 # start Ryu Openflow controller as Remote Controller for the DCNetwork
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200899 def startRyu(self, learning_switch=True):
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100900 # start Ryu controller with rest-API
901 python_install_path = site.getsitepackages()[0]
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100902 # ryu default learning switch
peusterm72f09882018-05-15 17:10:27 +0200903 # ryu_path = python_install_path + '/ryu/app/simple_switch_13.py'
904 # custom learning switch that installs a default NORMAL action in the
905 # ovs switches
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100906 dir_path = os.path.dirname(os.path.realpath(__file__))
907 ryu_path = dir_path + '/son_emu_simple_switch_13.py'
peustermde14f332016-03-15 16:14:21 +0100908 ryu_path2 = python_install_path + '/ryu/app/ofctl_rest.py'
stevenvanrossem9ebd0942016-02-22 10:13:05 +0100909 # change the default Openflow controller port to 6653 (official IANA-assigned port number), as used by Mininet
910 # Ryu still uses 6633 as default
911 ryu_option = '--ofp-tcp-listen-port'
912 ryu_of_port = '6653'
peustermde14f332016-03-15 16:14:21 +0100913 ryu_cmd = 'ryu-manager'
peustermef6629e2016-03-14 17:21:56 +0100914 FNULL = open("/tmp/ryu.log", 'w')
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200915 if learning_switch:
peusterm72f09882018-05-15 17:10:27 +0200916 self.ryu_process = Popen(
917 [ryu_cmd, ryu_path, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100918 LOG.debug('starting ryu-controller with {0}'.format(ryu_path))
919 LOG.debug('starting ryu-controller with {0}'.format(ryu_path2))
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200920 else:
stevenvanrossem73efd192016-06-29 01:44:07 +0200921 # no learning switch, but with rest api
peusterm72f09882018-05-15 17:10:27 +0200922 self.ryu_process = Popen(
923 [ryu_cmd, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
stevenvanrossemc3a344f2016-11-04 19:34:47 +0100924 LOG.debug('starting ryu-controller with {0}'.format(ryu_path2))
peusterm391773a2016-03-14 17:40:43 +0100925 time.sleep(1)
926
peusterm8b04b532016-07-19 16:55:38 +0200927 def killRyu(self):
928 """
929 Stop the Ryu controller that might be started by son-emu.
930 :return:
931 """
932 # try it nicely
peustermde14f332016-03-15 16:14:21 +0100933 if self.ryu_process is not None:
peusterm391773a2016-03-14 17:40:43 +0100934 self.ryu_process.terminate()
935 self.ryu_process.kill()
peusterm8b04b532016-07-19 16:55:38 +0200936 # ensure its death ;-)
stevenvanrossem89706802016-07-19 02:54:45 +0200937 Popen(['pkill', '-f', 'ryu-manager'])
peusterm391773a2016-03-14 17:40:43 +0100938
stevenvanrossem27b6d952016-05-10 16:37:57 +0200939 def ryu_REST(self, prefix, dpid=None, data=None):
stevenvanrossem27b6d952016-05-10 16:37:57 +0200940
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200941 if dpid:
942 url = self.ryu_REST_api + '/' + str(prefix) + '/' + str(dpid)
943 else:
944 url = self.ryu_REST_api + '/' + str(prefix)
945 if data:
946 req = self.RyuSession.post(url, json=data)
947 else:
948 req = self.RyuSession.get(url)
949
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200950 # do extra logging if status code is not 200 (OK)
951 if req.status_code is not requests.codes.ok:
952 logging.info(
953 'type {0} encoding: {1} text: {2} headers: {3} history: {4}'.format(req.headers['content-type'],
954 req.encoding, req.text,
955 req.headers, req.history))
956 LOG.info('url: {0}'.format(str(url)))
peusterm72f09882018-05-15 17:10:27 +0200957 if data:
958 LOG.info('POST: {0}'.format(str(data)))
959 LOG.info('status: {0} reason: {1}'.format(
960 req.status_code, req.reason))
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200961
962 if 'json' in req.headers['content-type']:
963 ret = req.json()
stevenvanrossem7cd3c252016-05-11 22:55:15 +0200964 return ret
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200965
966 ret = req.text.rstrip()
967 return ret
968
stevenvanrossem27b6d952016-05-10 16:37:57 +0200969 # need to respect that some match fields must be integers
970 # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#description-of-match-and-actions
peusterm72f09882018-05-15 17:10:27 +0200971
stevenvanrossem27b6d952016-05-10 16:37:57 +0200972 def _parse_match(self, match):
973 matches = match.split(',')
974 dict = {}
975 for m in matches:
976 match = m.split('=')
977 if len(match) == 2:
978 try:
979 m2 = int(match[1], 0)
peusterm72f09882018-05-15 17:10:27 +0200980 except BaseException:
stevenvanrossem27b6d952016-05-10 16:37:57 +0200981 m2 = match[1]
982
peusterm72f09882018-05-15 17:10:27 +0200983 dict.update({match[0]: m2})
stevenvanrossem27b6d952016-05-10 16:37:57 +0200984 return dict
985
peusterm72f09882018-05-15 17:10:27 +0200986 def find_connected_dc_interface(
987 self, vnf_src_name, vnf_src_interface=None):
stevenvanrossem17b6e882017-05-04 16:51:34 +0200988
989 if vnf_src_interface is None:
990 # take first interface by default
991 connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0]
992 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
993 vnf_src_interface = link_dict[0]['src_port_id']
994
stevenvanrossem566779d2016-11-07 06:33:44 +0100995 for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name):
996 link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw]
997 for link in link_dict:
998 if (link_dict[link]['src_port_id'] == vnf_src_interface or
peusterm72f09882018-05-15 17:10:27 +0200999 link_dict[link]['src_port_name'] == vnf_src_interface):
1000 # Fix: we might also get interface names, e.g, from a son-emu-cli call
stevenvanrossem566779d2016-11-07 06:33:44 +01001001 # found the right link and connected switch
stevenvanrossem566779d2016-11-07 06:33:44 +01001002 src_sw_inport_name = link_dict[link]['dst_port_name']
stevenvanrossem7062cee2016-12-22 10:31:38 +01001003 return src_sw_inport_name