blob: f8280c354b48c079406354c933518dc1fe7363b8 [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).
stevenvanrossemc5a536a2016-02-16 14:52:39 +010026import logging
peusterm72f09882018-05-15 17:10:27 +020027from mininet.node import OVSSwitch
stevenvanrossemc5a536a2016-02-16 14:52:39 +010028import ast
stevenvanrossem9315da42016-04-11 12:10:06 +020029import time
peusterm72f09882018-05-15 17:10:27 +020030from prometheus_client import Gauge, CollectorRegistry, \
31 pushadd_to_gateway, delete_from_gateway
stevenvanrossema24b4372016-04-14 09:55:20 +020032import threading
stevenvanrossemc63c5492017-05-08 16:10:13 +020033from subprocess import Popen
stevenvanrossemb098cb52016-04-15 13:28:23 +020034import os
stevenvanrossembeba14d2017-01-28 15:40:30 +010035import docker
stevenvanrossemfb8c8392017-01-28 17:29:11 +010036import json
stevenvanrossem5d61e262017-05-07 22:40:44 +020037from copy import deepcopy
stevenvanrossem461941c2016-05-10 11:41:29 +020038
peusterm5b428742017-06-16 10:08:11 +020039logging.basicConfig()
stevenvanrossemc5a536a2016-02-16 14:52:39 +010040
41"""
stevenvanrossema24b4372016-04-14 09:55:20 +020042class to read openflow stats from the Ryu controller of the DCNetwork
stevenvanrossemc5a536a2016-02-16 14:52:39 +010043"""
44
stevenvanrossema5aeb372016-08-18 17:32:24 +020045PUSHGATEWAY_PORT = 9091
stevenvanrosseme50b0a72016-08-18 17:42:50 +020046# we cannot use port 8080 because ryu-ofrest api is already using that one
47CADVISOR_PORT = 8081
stevenvanrossema5aeb372016-08-18 17:32:24 +020048
stevenvanrossemb7715d32016-08-26 16:22:21 +020049COOKIE_MASK = 0xffffffff
50
peusterm72f09882018-05-15 17:10:27 +020051
stevenvanrossemc5a536a2016-02-16 14:52:39 +010052class DCNetworkMonitor():
53 def __init__(self, net):
54 self.net = net
stevenvanrossembeba14d2017-01-28 15:40:30 +010055 self.dockercli = docker.from_env()
stevenvanrossem27b6d952016-05-10 16:37:57 +020056
stevenvanrossema5aeb372016-08-18 17:32:24 +020057 # pushgateway address
58 self.pushgateway = 'localhost:{0}'.format(PUSHGATEWAY_PORT)
stevenvanrosseme131bf52016-07-14 11:42:09 +020059
stevenvanrossema24b4372016-04-14 09:55:20 +020060 # supported Prometheus metrics
stevenvanrossem300e1e52016-04-22 22:17:51 +020061 self.registry = CollectorRegistry()
stevenvanrossema24b4372016-04-14 09:55:20 +020062 self.prom_tx_packet_count = Gauge('sonemu_tx_count_packets', 'Total number of packets sent',
stevenvanrossem461941c2016-05-10 11:41:29 +020063 ['vnf_name', 'vnf_interface', 'flow_id'], registry=self.registry)
stevenvanrossema24b4372016-04-14 09:55:20 +020064 self.prom_rx_packet_count = Gauge('sonemu_rx_count_packets', 'Total number of packets received',
stevenvanrossem461941c2016-05-10 11:41:29 +020065 ['vnf_name', 'vnf_interface', 'flow_id'], registry=self.registry)
stevenvanrossema24b4372016-04-14 09:55:20 +020066 self.prom_tx_byte_count = Gauge('sonemu_tx_count_bytes', 'Total number of bytes sent',
stevenvanrossem461941c2016-05-10 11:41:29 +020067 ['vnf_name', 'vnf_interface', 'flow_id'], registry=self.registry)
stevenvanrossema24b4372016-04-14 09:55:20 +020068 self.prom_rx_byte_count = Gauge('sonemu_rx_count_bytes', 'Total number of bytes received',
stevenvanrossem461941c2016-05-10 11:41:29 +020069 ['vnf_name', 'vnf_interface', 'flow_id'], registry=self.registry)
stevenvanrossema24b4372016-04-14 09:55:20 +020070
peusterm72f09882018-05-15 17:10:27 +020071 self.prom_metrics = {'tx_packets': self.prom_tx_packet_count, 'rx_packets': self.prom_rx_packet_count,
72 'tx_bytes': self.prom_tx_byte_count, 'rx_bytes': self.prom_rx_byte_count}
stevenvanrossema24b4372016-04-14 09:55:20 +020073
74 # list of installed metrics to monitor
75 # each entry can contain this data
76 '''
77 {
78 switch_dpid = 0
79 vnf_name = None
80 vnf_interface = None
81 previous_measurement = 0
82 previous_monitor_time = 0
83 metric_key = None
84 mon_port = None
85 }
86 '''
stevenvanrossem300e1e52016-04-22 22:17:51 +020087 self.monitor_lock = threading.Lock()
stevenvanrossem461941c2016-05-10 11:41:29 +020088 self.monitor_flow_lock = threading.Lock()
stevenvanrossemc6abf132016-04-14 11:15:58 +020089 self.network_metrics = []
stevenvanrossem461941c2016-05-10 11:41:29 +020090 self.flow_metrics = []
stevenvanrossemfb8c8392017-01-28 17:29:11 +010091 self.skewmon_metrics = {}
stevenvanrossema24b4372016-04-14 09:55:20 +020092
93 # start monitoring thread
stevenvanrossemb098cb52016-04-15 13:28:23 +020094 self.start_monitoring = True
stevenvanrossema24b4372016-04-14 09:55:20 +020095 self.monitor_thread = threading.Thread(target=self.get_network_metrics)
96 self.monitor_thread.start()
97
peusterm72f09882018-05-15 17:10:27 +020098 self.monitor_flow_thread = threading.Thread(
99 target=self.get_flow_metrics)
stevenvanrossem461941c2016-05-10 11:41:29 +0200100 self.monitor_flow_thread.start()
101
stevenvanrossemc6abf132016-04-14 11:15:58 +0200102 # helper tools
peusterm72f09882018-05-15 17:10:27 +0200103 # cAdvisor, Prometheus pushgateway are started as external container,
104 # to gather monitoring metric in son-emu
stevenvanrosseme50b0a72016-08-18 17:42:50 +0200105 self.pushgateway_process = self.start_PushGateway()
106 self.cadvisor_process = self.start_cAdvisor()
stevenvanrossem89706802016-07-19 02:54:45 +0200107
stevenvanrossemed711fd2016-04-11 16:59:29 +0200108 # first set some parameters, before measurement can start
peusterm72f09882018-05-15 17:10:27 +0200109
110 def setup_flow(self, vnf_name, vnf_interface=None,
111 metric='tx_packets', cookie=0):
stevenvanrossem461941c2016-05-10 11:41:29 +0200112
113 flow_metric = {}
114
115 # check if port is specified (vnf:port)
116 if vnf_interface is None:
117 # take first interface by default
118 connected_sw = self.net.DCNetwork_graph.neighbors(vnf_name)[0]
119 link_dict = self.net.DCNetwork_graph[vnf_name][connected_sw]
120 vnf_interface = link_dict[0]['src_port_id']
121
122 flow_metric['vnf_name'] = vnf_name
123 flow_metric['vnf_interface'] = vnf_interface
124
125 vnf_switch = None
126 for connected_sw in self.net.DCNetwork_graph.neighbors(vnf_name):
127 link_dict = self.net.DCNetwork_graph[vnf_name][connected_sw]
128 for link in link_dict:
stevenvanrossem461941c2016-05-10 11:41:29 +0200129 if link_dict[link]['src_port_id'] == vnf_interface:
130 # found the right link and connected switch
stevenvanrossem461941c2016-05-10 11:41:29 +0200131 vnf_switch = connected_sw
132 flow_metric['mon_port'] = link_dict[link]['dst_port_nr']
133 break
134
135 if not vnf_switch:
peusterm72f09882018-05-15 17:10:27 +0200136 logging.exception("vnf switch of {0}:{1} not found!".format(
137 vnf_name, vnf_interface))
138 return "vnf switch of {0}:{1} not found!".format(
139 vnf_name, vnf_interface)
stevenvanrossem461941c2016-05-10 11:41:29 +0200140
141 try:
142 # default port direction to monitor
143 if metric is None:
144 metric = 'tx_packets'
145
146 next_node = self.net.getNodeByName(vnf_switch)
147
148 if not isinstance(next_node, OVSSwitch):
peusterm72f09882018-05-15 17:10:27 +0200149 logging.info(
150 "vnf: {0} is not connected to switch".format(vnf_name))
stevenvanrossem461941c2016-05-10 11:41:29 +0200151 return
152
153 flow_metric['previous_measurement'] = 0
154 flow_metric['previous_monitor_time'] = 0
155
156 flow_metric['switch_dpid'] = int(str(next_node.dpid), 16)
157 flow_metric['metric_key'] = metric
158 flow_metric['cookie'] = cookie
159
160 self.monitor_flow_lock.acquire()
161 self.flow_metrics.append(flow_metric)
162 self.monitor_flow_lock.release()
163
peusterm72f09882018-05-15 17:10:27 +0200164 logging.info('Started monitoring flow:{3} {2} on {0}:{1}'.format(
165 vnf_name, vnf_interface, metric, cookie))
166 return 'Started monitoring flow:{3} {2} on {0}:{1}'.format(
167 vnf_name, vnf_interface, metric, cookie)
stevenvanrossem461941c2016-05-10 11:41:29 +0200168
169 except Exception as ex:
170 logging.exception("setup_metric error.")
171 return ex.message
172
stevenvanrossem9c8a4122016-07-16 03:23:13 +0200173 def stop_flow(self, vnf_name, vnf_interface=None, metric=None, cookie=0,):
174
175 # check if port is specified (vnf:port)
176 if vnf_interface is None and metric is not None:
177 # take first interface by default
178 connected_sw = self.net.DCNetwork_graph.neighbors(vnf_name)[0]
179 link_dict = self.net.DCNetwork_graph[vnf_name][connected_sw]
180 vnf_interface = link_dict[0]['src_port_id']
181
stevenvanrossem1ef77022016-05-12 16:36:10 +0200182 for flow_dict in self.flow_metrics:
183 if flow_dict['vnf_name'] == vnf_name and flow_dict['vnf_interface'] == vnf_interface \
184 and flow_dict['metric_key'] == metric and flow_dict['cookie'] == cookie:
185
186 self.monitor_flow_lock.acquire()
187
188 self.flow_metrics.remove(flow_dict)
189
stevenvanrossembeba14d2017-01-28 15:40:30 +0100190 # set metric to NaN
191 self.prom_metrics[flow_dict['metric_key']]. \
192 labels(vnf_name=vnf_name, vnf_interface=vnf_interface, flow_id=cookie). \
193 set(float('nan'))
stevenvanrossem1ef77022016-05-12 16:36:10 +0200194
peusterm72f09882018-05-15 17:10:27 +0200195 delete_from_gateway(
196 self.pushgateway, job='sonemu-SDNcontroller')
stevenvanrossem1ef77022016-05-12 16:36:10 +0200197
198 self.monitor_flow_lock.release()
199
peusterm72f09882018-05-15 17:10:27 +0200200 logging.info('Stopped monitoring flow {3}: {2} on {0}:{1}'.format(
201 vnf_name, vnf_interface, metric, cookie))
202 return 'Stopped monitoring flow {3}: {2} on {0}:{1}'.format(
203 vnf_name, vnf_interface, metric, cookie)
stevenvanrossem1ef77022016-05-12 16:36:10 +0200204
peusterm72f09882018-05-15 17:10:27 +0200205 return 'Error stopping monitoring flow: {0} on {1}:{2}'.format(
206 metric, vnf_name, vnf_interface)
stevenvanrossem461941c2016-05-10 11:41:29 +0200207
208 # first set some parameters, before measurement can start
peusterm72f09882018-05-15 17:10:27 +0200209
stevenvanrossema24b4372016-04-14 09:55:20 +0200210 def setup_metric(self, vnf_name, vnf_interface=None, metric='tx_packets'):
211
212 network_metric = {}
213
stevenvanrossem9315da42016-04-11 12:10:06 +0200214 # check if port is specified (vnf:port)
stevenvanrossemc6ace2d2017-04-21 13:47:06 +0200215 if vnf_interface is None or vnf_interface == '':
stevenvanrossem9315da42016-04-11 12:10:06 +0200216 # take first interface by default
217 connected_sw = self.net.DCNetwork_graph.neighbors(vnf_name)[0]
218 link_dict = self.net.DCNetwork_graph[vnf_name][connected_sw]
219 vnf_interface = link_dict[0]['src_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200220
stevenvanrossema24b4372016-04-14 09:55:20 +0200221 network_metric['vnf_name'] = vnf_name
222 network_metric['vnf_interface'] = vnf_interface
stevenvanrossema24b4372016-04-14 09:55:20 +0200223
stevenvanrossem9315da42016-04-11 12:10:06 +0200224 for connected_sw in self.net.DCNetwork_graph.neighbors(vnf_name):
225 link_dict = self.net.DCNetwork_graph[vnf_name][connected_sw]
226 for link in link_dict:
stevenvanrossem9315da42016-04-11 12:10:06 +0200227 if link_dict[link]['src_port_id'] == vnf_interface:
228 # found the right link and connected switch
stevenvanrossem307aa1f2016-05-06 10:35:15 +0200229 network_metric['mon_port'] = link_dict[link]['dst_port_nr']
stevenvanrossem9315da42016-04-11 12:10:06 +0200230 break
231
stevenvanrossema24b4372016-04-14 09:55:20 +0200232 if 'mon_port' not in network_metric:
peusterm72f09882018-05-15 17:10:27 +0200233 logging.exception("vnf interface {0}:{1} not found!".format(
234 vnf_name, vnf_interface))
235 return "vnf interface {0}:{1} not found!".format(
236 vnf_name, vnf_interface)
stevenvanrossema24b4372016-04-14 09:55:20 +0200237
stevenvanrossem9315da42016-04-11 12:10:06 +0200238 try:
239 # default port direction to monitor
stevenvanrossemed711fd2016-04-11 16:59:29 +0200240 if metric is None:
stevenvanrossema24b4372016-04-14 09:55:20 +0200241 metric = 'tx_packets'
stevenvanrossem9315da42016-04-11 12:10:06 +0200242
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100243 vnf_switch = self.net.DCNetwork_graph.neighbors(str(vnf_name))
244
245 if len(vnf_switch) > 1:
246 logging.info("vnf: {0} has multiple ports".format(vnf_name))
247 return
248 elif len(vnf_switch) == 0:
249 logging.info("vnf: {0} is not connected".format(vnf_name))
250 return
251 else:
252 vnf_switch = vnf_switch[0]
253 next_node = self.net.getNodeByName(vnf_switch)
254
stevenvanrossemed711fd2016-04-11 16:59:29 +0200255 if not isinstance(next_node, OVSSwitch):
peusterm72f09882018-05-15 17:10:27 +0200256 logging.info(
257 "vnf: {0} is not connected to switch".format(vnf_name))
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100258 return
259
stevenvanrossema24b4372016-04-14 09:55:20 +0200260 network_metric['previous_measurement'] = 0
261 network_metric['previous_monitor_time'] = 0
stevenvanrossemb098cb52016-04-15 13:28:23 +0200262
stevenvanrossema24b4372016-04-14 09:55:20 +0200263 network_metric['switch_dpid'] = int(str(next_node.dpid), 16)
264 network_metric['metric_key'] = metric
stevenvanrossemb098cb52016-04-15 13:28:23 +0200265
stevenvanrossem300e1e52016-04-22 22:17:51 +0200266 self.monitor_lock.acquire()
stevenvanrossema24b4372016-04-14 09:55:20 +0200267 self.network_metrics.append(network_metric)
stevenvanrossem300e1e52016-04-22 22:17:51 +0200268 self.monitor_lock.release()
269
peusterm72f09882018-05-15 17:10:27 +0200270 logging.info('Started monitoring: {2} on {0}:{1}'.format(
271 vnf_name, vnf_interface, metric))
272 return 'Started monitoring: {2} on {0}:{1}'.format(
273 vnf_name, vnf_interface, metric)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100274
stevenvanrossemed711fd2016-04-11 16:59:29 +0200275 except Exception as ex:
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200276 logging.exception("setup_metric error.")
stevenvanrossemed711fd2016-04-11 16:59:29 +0200277 return ex.message
stevenvanrossem9315da42016-04-11 12:10:06 +0200278
stevenvanrossem461941c2016-05-10 11:41:29 +0200279 def stop_metric(self, vnf_name, vnf_interface=None, metric=None):
stevenvanrossem300e1e52016-04-22 22:17:51 +0200280
stevenvanrossem9c8a4122016-07-16 03:23:13 +0200281 # check if port is specified (vnf:port)
282 if vnf_interface is None and metric is not None:
283 # take first interface by default
284 connected_sw = self.net.DCNetwork_graph.neighbors(vnf_name)[0]
285 link_dict = self.net.DCNetwork_graph[vnf_name][connected_sw]
286 vnf_interface = link_dict[0]['src_port_id']
287
stevenvanrossem5d61e262017-05-07 22:40:44 +0200288 for metric_dict in deepcopy(self.network_metrics):
stevenvanrossemb098cb52016-04-15 13:28:23 +0200289 if metric_dict['vnf_name'] == vnf_name and metric_dict['vnf_interface'] == vnf_interface \
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200290 and metric_dict['metric_key'] == metric:
291
stevenvanrossem300e1e52016-04-22 22:17:51 +0200292 self.monitor_lock.acquire()
293
stevenvanrossemb098cb52016-04-15 13:28:23 +0200294 self.network_metrics.remove(metric_dict)
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200295
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200296 # set values to NaN, prometheus api currently does not support removal of metrics
peusterm72f09882018-05-15 17:10:27 +0200297 # self.prom_metrics[metric_dict['metric_key']].labels(vnf_name, vnf_interface).set(float('nan'))
stevenvanrossembeba14d2017-01-28 15:40:30 +0100298 self.prom_metrics[metric_dict['metric_key']]. \
299 labels(vnf_name=vnf_name, vnf_interface=vnf_interface, flow_id=None). \
300 set(float('nan'))
stevenvanrossem300e1e52016-04-22 22:17:51 +0200301
302 # this removes the complete metric, all labels...
303 # 1 single monitor job for all metrics of the SDN controller
304 # we can only remove from the pushgateway grouping keys(labels) which we have defined for the add_to_pushgateway
305 # we can not specify labels from the metrics to be removed
peusterm72f09882018-05-15 17:10:27 +0200306 # if we need to remove the metrics seperatelty, we need to give
307 # them a separate grouping key, and probably a diffferent
308 # registry also
309 delete_from_gateway(
310 self.pushgateway, job='sonemu-SDNcontroller')
stevenvanrossem300e1e52016-04-22 22:17:51 +0200311
312 self.monitor_lock.release()
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200313
peusterm72f09882018-05-15 17:10:27 +0200314 logging.info('Stopped monitoring: {2} on {0}:{1}'.format(
315 vnf_name, vnf_interface, metric))
316 return 'Stopped monitoring: {2} on {0}:{1}'.format(
317 vnf_name, vnf_interface, metric)
stevenvanrossem9315da42016-04-11 12:10:06 +0200318
stevenvanrossem461941c2016-05-10 11:41:29 +0200319 # delete everything from this vnf
320 elif metric_dict['vnf_name'] == vnf_name and vnf_interface is None and metric is None:
321 self.monitor_lock.acquire()
322 self.network_metrics.remove(metric_dict)
peusterm72f09882018-05-15 17:10:27 +0200323 logging.info('remove metric from monitor: vnf_name:{0} vnf_interface:{1} mon_port:{2}'.format(
324 metric_dict['vnf_name'], metric_dict['vnf_interface'], metric_dict['mon_port']))
stevenvanrossem461941c2016-05-10 11:41:29 +0200325
peusterm72f09882018-05-15 17:10:27 +0200326 delete_from_gateway(
327 self.pushgateway, job='sonemu-SDNcontroller')
stevenvanrossem461941c2016-05-10 11:41:29 +0200328 self.monitor_lock.release()
stevenvanrossem5d61e262017-05-07 22:40:44 +0200329 continue
stevenvanrossem461941c2016-05-10 11:41:29 +0200330
stevenvanrossem5d61e262017-05-07 22:40:44 +0200331 if vnf_interface is None and metric is None:
332 logging.info('Stopped monitoring vnf: {0}'.format(vnf_name))
333 return 'Stopped monitoring: {0}'.format(vnf_name)
334 else:
peusterm72f09882018-05-15 17:10:27 +0200335 return 'Error stopping monitoring metric: {0} on {1}:{2}'.format(
336 metric, vnf_name, vnf_interface)
stevenvanrossemb098cb52016-04-15 13:28:23 +0200337
stevenvanrossem461941c2016-05-10 11:41:29 +0200338 def get_flow_metrics(self):
peusterm04e5f982018-10-31 19:50:16 +0100339 """
340 Get all metrics defined in the list and export it to Prometheus.
341 """
stevenvanrossem461941c2016-05-10 11:41:29 +0200342 while self.start_monitoring:
343
344 self.monitor_flow_lock.acquire()
345
346 for flow_dict in self.flow_metrics:
347 data = {}
348
349 data['cookie'] = flow_dict['cookie']
stevenvanrossemb7715d32016-08-26 16:22:21 +0200350 data['cookie_mask'] = COOKIE_MASK
stevenvanrossem461941c2016-05-10 11:41:29 +0200351
352 if 'tx' in flow_dict['metric_key']:
peusterm72f09882018-05-15 17:10:27 +0200353 data['match'] = {'in_port': flow_dict['mon_port']}
stevenvanrossem461941c2016-05-10 11:41:29 +0200354 elif 'rx' in flow_dict['metric_key']:
355 data['out_port'] = flow_dict['mon_port']
356
stevenvanrossem461941c2016-05-10 11:41:29 +0200357 # query Ryu
peusterm72f09882018-05-15 17:10:27 +0200358 ret = self.net.ryu_REST(
359 'stats/flow', dpid=flow_dict['switch_dpid'], data=data)
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200360 if isinstance(ret, dict):
361 flow_stat_dict = ret
362 elif isinstance(ret, basestring):
363 flow_stat_dict = ast.literal_eval(ret.rstrip())
364 else:
365 flow_stat_dict = None
366
stevenvanrosseme131bf52016-07-14 11:42:09 +0200367 logging.debug('received flow stat:{0} '.format(flow_stat_dict))
stevenvanrossem3fc13932016-08-09 23:39:16 +0200368
stevenvanrossem461941c2016-05-10 11:41:29 +0200369 self.set_flow_metric(flow_dict, flow_stat_dict)
370
stevenvanrossembeba14d2017-01-28 15:40:30 +0100371 try:
372 if len(self.flow_metrics) > 0:
peusterm72f09882018-05-15 17:10:27 +0200373 pushadd_to_gateway(
374 self.pushgateway, job='sonemu-SDNcontroller', registry=self.registry)
375 except Exception as e:
376 logging.warning(
377 "Pushgateway not reachable: {0} {1}".format(Exception, e))
stevenvanrossembeba14d2017-01-28 15:40:30 +0100378
stevenvanrossem461941c2016-05-10 11:41:29 +0200379 self.monitor_flow_lock.release()
380 time.sleep(1)
381
stevenvanrossema24b4372016-04-14 09:55:20 +0200382 def get_network_metrics(self):
stevenvanrossemb098cb52016-04-15 13:28:23 +0200383 while self.start_monitoring:
stevenvanrossem300e1e52016-04-22 22:17:51 +0200384
385 self.monitor_lock.acquire()
386
stevenvanrossema24b4372016-04-14 09:55:20 +0200387 # group metrics by dpid to optimize the rest api calls
peusterm72f09882018-05-15 17:10:27 +0200388 dpid_list = [metric_dict['switch_dpid']
389 for metric_dict in self.network_metrics]
stevenvanrossema24b4372016-04-14 09:55:20 +0200390 dpid_set = set(dpid_list)
391
392 for dpid in dpid_set:
393
394 # query Ryu
stevenvanrossem27b6d952016-05-10 16:37:57 +0200395 ret = self.net.ryu_REST('stats/port', dpid=dpid)
stevenvanrossemb7715d32016-08-26 16:22:21 +0200396 if isinstance(ret, dict):
397 port_stat_dict = ret
398 elif isinstance(ret, basestring):
399 port_stat_dict = ast.literal_eval(ret.rstrip())
400 else:
401 port_stat_dict = None
stevenvanrossema24b4372016-04-14 09:55:20 +0200402
403 metric_list = [metric_dict for metric_dict in self.network_metrics
peusterm72f09882018-05-15 17:10:27 +0200404 if int(metric_dict['switch_dpid']) == int(dpid)]
stevenvanrosseme131bf52016-07-14 11:42:09 +0200405
stevenvanrossema24b4372016-04-14 09:55:20 +0200406 for metric_dict in metric_list:
407 self.set_network_metric(metric_dict, port_stat_dict)
408
stevenvanrossembeba14d2017-01-28 15:40:30 +0100409 try:
410 if len(self.network_metrics) > 0:
peusterm72f09882018-05-15 17:10:27 +0200411 pushadd_to_gateway(
412 self.pushgateway, job='sonemu-SDNcontroller', registry=self.registry)
413 except Exception as e:
414 logging.warning(
415 "Pushgateway not reachable: {0} {1}".format(Exception, e))
stevenvanrossembeba14d2017-01-28 15:40:30 +0100416
stevenvanrossem300e1e52016-04-22 22:17:51 +0200417 self.monitor_lock.release()
stevenvanrossema24b4372016-04-14 09:55:20 +0200418 time.sleep(1)
419
peusterm72f09882018-05-15 17:10:27 +0200420 # add metric to the list to export to Prometheus, parse the Ryu port-stats
421 # reply
stevenvanrossema24b4372016-04-14 09:55:20 +0200422 def set_network_metric(self, metric_dict, port_stat_dict):
stevenvanrossemb098cb52016-04-15 13:28:23 +0200423 # vnf tx is the datacenter switch rx and vice-versa
424 metric_key = self.switch_tx_rx(metric_dict['metric_key'])
stevenvanrossema24b4372016-04-14 09:55:20 +0200425 switch_dpid = metric_dict['switch_dpid']
426 vnf_name = metric_dict['vnf_name']
427 vnf_interface = metric_dict['vnf_interface']
stevenvanrossema24b4372016-04-14 09:55:20 +0200428 previous_monitor_time = metric_dict['previous_monitor_time']
429 mon_port = metric_dict['mon_port']
stevenvanrossema24b4372016-04-14 09:55:20 +0200430 for port_stat in port_stat_dict[str(switch_dpid)]:
stevenvanrossemc6ace2d2017-04-21 13:47:06 +0200431 # ovs output also gives back 'LOCAL' port
432 if port_stat['port_no'] == 'LOCAL':
433 continue
stevenvanrossema24b4372016-04-14 09:55:20 +0200434 if int(port_stat['port_no']) == int(mon_port):
peusterm72f09882018-05-15 17:10:27 +0200435 port_uptime = port_stat['duration_sec'] + \
436 port_stat['duration_nsec'] * 10 ** (-9)
stevenvanrossema24b4372016-04-14 09:55:20 +0200437 this_measurement = int(port_stat[metric_key])
stevenvanrossema24b4372016-04-14 09:55:20 +0200438
439 # set prometheus metric
stevenvanrossem300e1e52016-04-22 22:17:51 +0200440 self.prom_metrics[metric_dict['metric_key']].\
stevenvanrossembeba14d2017-01-28 15:40:30 +0100441 labels(vnf_name=vnf_name, vnf_interface=vnf_interface, flow_id=None).\
stevenvanrossem300e1e52016-04-22 22:17:51 +0200442 set(this_measurement)
stevenvanrossem300e1e52016-04-22 22:17:51 +0200443
stevenvanrosseme131bf52016-07-14 11:42:09 +0200444 # also the rate is calculated here, but not used for now
445 # (rate can be easily queried from prometheus also)
stevenvanrossema24b4372016-04-14 09:55:20 +0200446 if previous_monitor_time <= 0 or previous_monitor_time >= port_uptime:
peusterm72f09882018-05-15 17:10:27 +0200447 metric_dict['previous_measurement'] = int(
448 port_stat[metric_key])
stevenvanrossema24b4372016-04-14 09:55:20 +0200449 metric_dict['previous_monitor_time'] = port_uptime
450 # do first measurement
peusterm72f09882018-05-15 17:10:27 +0200451 # time.sleep(1)
452 # self.monitor_lock.release()
stevenvanrossemc721f282016-08-30 10:56:05 +0200453 # rate cannot be calculated yet (need a first measurement)
stevenvanrossema24b4372016-04-14 09:55:20 +0200454 metric_dict['previous_measurement'] = this_measurement
455 metric_dict['previous_monitor_time'] = port_uptime
stevenvanrossemc721f282016-08-30 10:56:05 +0200456 return
stevenvanrossema24b4372016-04-14 09:55:20 +0200457
peusterm72f09882018-05-15 17:10:27 +0200458 logging.exception('metric {0} not found on {1}:{2}'.format(
459 metric_key, vnf_name, vnf_interface))
460 logging.exception(
461 'monport:{0}, dpid:{1}'.format(mon_port, switch_dpid))
462 logging.exception(
463 'monitored network_metrics:{0}'.format(self.network_metrics))
stevenvanrossemc721f282016-08-30 10:56:05 +0200464 logging.exception('port dict:{0}'.format(port_stat_dict))
peusterm72f09882018-05-15 17:10:27 +0200465 return 'metric {0} not found on {1}:{2}'.format(
466 metric_key, vnf_name, vnf_interface)
stevenvanrossema24b4372016-04-14 09:55:20 +0200467
stevenvanrossem461941c2016-05-10 11:41:29 +0200468 def set_flow_metric(self, metric_dict, flow_stat_dict):
469 # vnf tx is the datacenter switch rx and vice-versa
stevenvanrossem461941c2016-05-10 11:41:29 +0200470 metric_key = metric_dict['metric_key']
471 switch_dpid = metric_dict['switch_dpid']
472 vnf_name = metric_dict['vnf_name']
473 vnf_interface = metric_dict['vnf_interface']
stevenvanrossem461941c2016-05-10 11:41:29 +0200474 cookie = metric_dict['cookie']
stevenvanrossema24b4372016-04-14 09:55:20 +0200475
stevenvanrossem9c8a4122016-07-16 03:23:13 +0200476 counter = 0
477 for flow_stat in flow_stat_dict[str(switch_dpid)]:
478 if 'bytes' in metric_key:
479 counter += flow_stat['byte_count']
480 elif 'packet' in metric_key:
481 counter += flow_stat['packet_count']
482
stevenvanrossemc721f282016-08-30 10:56:05 +0200483 # flow_uptime disabled for now (can give error)
peusterm72f09882018-05-15 17:10:27 +0200484 # flow_stat = flow_stat_dict[str(switch_dpid)][0]
485 # flow_uptime = flow_stat['duration_sec'] + flow_stat['duration_nsec'] * 10 ** (-9)
stevenvanrossem461941c2016-05-10 11:41:29 +0200486
487 self.prom_metrics[metric_dict['metric_key']]. \
stevenvanrossembeba14d2017-01-28 15:40:30 +0100488 labels(vnf_name=vnf_name, vnf_interface=vnf_interface, flow_id=cookie). \
stevenvanrossem461941c2016-05-10 11:41:29 +0200489 set(counter)
stevenvanrossemc6abf132016-04-14 11:15:58 +0200490
stevenvanrosseme50b0a72016-08-18 17:42:50 +0200491 def start_Prometheus(self, port=9090):
peusterm72f09882018-05-15 17:10:27 +0200492 # prometheus.yml configuration file is located in the same directory as
493 # this file
stevenvanrossemc6abf132016-04-14 11:15:58 +0200494 cmd = ["docker",
495 "run",
496 "--rm",
497 "-p", "{0}:9090".format(port),
peusterm72f09882018-05-15 17:10:27 +0200498 "-v", "{0}/prometheus.yml:/etc/prometheus/prometheus.yml".format(
499 os.path.dirname(os.path.abspath(__file__))),
500 "-v", "{0}/profile.rules:/etc/prometheus/profile.rules".format(
501 os.path.dirname(os.path.abspath(__file__))),
stevenvanrossemc6abf132016-04-14 11:15:58 +0200502 "--name", "prometheus",
503 "prom/prometheus"
504 ]
stevenvanrossemb098cb52016-04-15 13:28:23 +0200505 logging.info('Start Prometheus container {0}'.format(cmd))
506 return Popen(cmd)
stevenvanrossemc6abf132016-04-14 11:15:58 +0200507
stevenvanrossema5aeb372016-08-18 17:32:24 +0200508 def start_PushGateway(self, port=PUSHGATEWAY_PORT):
stevenvanrossemadfd06f2016-04-22 10:39:08 +0200509 cmd = ["docker",
510 "run",
511 "-d",
512 "-p", "{0}:9091".format(port),
513 "--name", "pushgateway",
stevenvanrossemc721f282016-08-30 10:56:05 +0200514 "--label", 'com.containernet=""',
stevenvanrossemadfd06f2016-04-22 10:39:08 +0200515 "prom/pushgateway"
516 ]
517
518 logging.info('Start Prometheus Push Gateway container {0}'.format(cmd))
519 return Popen(cmd)
520
stevenvanrosseme50b0a72016-08-18 17:42:50 +0200521 def start_cAdvisor(self, port=CADVISOR_PORT):
stevenvanrossemc6abf132016-04-14 11:15:58 +0200522 cmd = ["docker",
523 "run",
524 "--rm",
525 "--volume=/:/rootfs:ro",
526 "--volume=/var/run:/var/run:rw",
527 "--volume=/sys:/sys:ro",
528 "--volume=/var/lib/docker/:/var/lib/docker:ro",
529 "--publish={0}:8080".format(port),
530 "--name=cadvisor",
peusterm72f09882018-05-15 17:10:27 +0200531 "--label", 'com.containernet=""',
stevenvanrossem6bed70f2017-04-24 03:14:44 +0200532 "--detach=true",
stevenvanrossem3df73e82017-04-22 19:37:29 +0200533 "google/cadvisor:latest",
peusterm72f09882018-05-15 17:10:27 +0200534 # "--storage_duration=1m0s",
535 # "--allow_dynamic_housekeeping=true",
536 # "--housekeeping_interval=1s",
stevenvanrossemc6abf132016-04-14 11:15:58 +0200537 ]
stevenvanrossemb098cb52016-04-15 13:28:23 +0200538 logging.info('Start cAdvisor container {0}'.format(cmd))
539 return Popen(cmd)
stevenvanrossemc6abf132016-04-14 11:15:58 +0200540
541 def stop(self):
stevenvanrossemb098cb52016-04-15 13:28:23 +0200542 # stop the monitoring thread
543 self.start_monitoring = False
544 self.monitor_thread.join()
stevenvanrossem461941c2016-05-10 11:41:29 +0200545 self.monitor_flow_thread.join()
stevenvanrossemb098cb52016-04-15 13:28:23 +0200546
peusterm72f09882018-05-15 17:10:27 +0200547 # these containers are used for monitoring but are started now outside
548 # of son-emu
stevenvanrossembeba14d2017-01-28 15:40:30 +0100549
stevenvanrossemadfd06f2016-04-22 10:39:08 +0200550 if self.pushgateway_process is not None:
551 logging.info('stopping pushgateway container')
stevenvanrossemadfd06f2016-04-22 10:39:08 +0200552 self._stop_container('pushgateway')
553
stevenvanrossemb098cb52016-04-15 13:28:23 +0200554 if self.cadvisor_process is not None:
555 logging.info('stopping cadvisor container')
stevenvanrossemb098cb52016-04-15 13:28:23 +0200556 self._stop_container('cadvisor')
557
peusterm72f09882018-05-15 17:10:27 +0200558 def switch_tx_rx(self, metric=''):
stevenvanrossemb098cb52016-04-15 13:28:23 +0200559 # when monitoring vnfs, the tx of the datacenter switch is actually the rx of the vnf
peusterm72f09882018-05-15 17:10:27 +0200560 # so we need to change the metric name to be consistent with the vnf rx
561 # or tx
stevenvanrossemb098cb52016-04-15 13:28:23 +0200562 if 'tx' in metric:
peusterm72f09882018-05-15 17:10:27 +0200563 metric = metric.replace('tx', 'rx')
stevenvanrossemb098cb52016-04-15 13:28:23 +0200564 elif 'rx' in metric:
peusterm72f09882018-05-15 17:10:27 +0200565 metric = metric.replace('rx', 'tx')
stevenvanrossemb098cb52016-04-15 13:28:23 +0200566
567 return metric
568
569 def _stop_container(self, name):
stevenvanrossemb098cb52016-04-15 13:28:23 +0200570
peusterm72f09882018-05-15 17:10:27 +0200571 # container = self.dockercli.containers.get(name)
572 # container.stop()
573 # container.remove(force=True)
stevenvanrossemce032e12017-04-05 17:31:20 +0200574
575 # the only robust way to stop these containers is via Popen, it seems
576 time.sleep(1)
577 cmd = ['docker', 'rm', '-f', name]
578 Popen(cmd)
579
stevenvanrossemfb8c8392017-01-28 17:29:11 +0100580 def update_skewmon(self, vnf_name, resource_name, action):
581
582 ret = ''
583
584 config_file_path = '/tmp/skewmon.cfg'
585 configfile = open(config_file_path, 'a+')
586 try:
587 config = json.load(configfile)
peusterm72f09882018-05-15 17:10:27 +0200588 except BaseException:
589 # not a valid json file or empty
stevenvanrossemfb8c8392017-01-28 17:29:11 +0100590 config = {}
591
peusterm72f09882018-05-15 17:10:27 +0200592 # initialize config file
stevenvanrossemfb8c8392017-01-28 17:29:11 +0100593 if len(self.skewmon_metrics) == 0:
594 config = {}
595 json.dump(config, configfile)
596 configfile.close()
597
598 docker_name = 'mn.' + vnf_name
599 vnf_container = self.dockercli.containers.get(docker_name)
600 key = resource_name + '_' + vnf_container.short_id
601 vnf_id = vnf_container.id
602
603 if action == 'start':
604 # add a new vnf to monitor
605 config[key] = dict(VNF_NAME=vnf_name,
peusterm72f09882018-05-15 17:10:27 +0200606 VNF_ID=vnf_id,
607 VNF_METRIC=resource_name)
608 ret = 'adding to skewness monitor: {0} {1} '.format(
609 vnf_name, resource_name)
stevenvanrossemfb8c8392017-01-28 17:29:11 +0100610 logging.info(ret)
611 elif action == 'stop':
612 # remove vnf to monitor
613 config.pop(key)
peusterm72f09882018-05-15 17:10:27 +0200614 ret = 'removing from skewness monitor: {0} {1} '.format(
615 vnf_name, resource_name)
stevenvanrossemfb8c8392017-01-28 17:29:11 +0100616 logging.info(ret)
617
618 self.skewmon_metrics = config
619 configfile = open(config_file_path, 'w')
620 json.dump(config, configfile)
621 configfile.close()
622
623 try:
624 skewmon_container = self.dockercli.containers.get('skewmon')
625
626 # remove container if config is empty
627 if len(config) == 0:
628 ret += 'stopping skewness monitor'
629 logging.info('stopping skewness monitor')
630 skewmon_container.remove(force=True)
631
632 except docker.errors.NotFound:
633 # start container if not running
634 ret += 'starting skewness monitor'
635 logging.info('starting skewness monitor')
peusterm72f09882018-05-15 17:10:27 +0200636 volumes = {'/sys/fs/cgroup': {'bind': '/sys/fs/cgroup', 'mode': 'ro'},
637 '/tmp/skewmon.cfg': {'bind': '/config.txt', 'mode': 'ro'}}
stevenvanrossemfb8c8392017-01-28 17:29:11 +0100638 self.dockercli.containers.run('skewmon',
639 detach=True,
640 volumes=volumes,
641 labels=['com.containernet'],
642 name='skewmon'
643 )
stevenvanrossema7f601c2017-02-09 13:54:42 +0100644 # Wait a while for containers to be completely started
645 started = False
646 wait_time = 0
647 while not started:
peusterm72f09882018-05-15 17:10:27 +0200648 list1 = self.dockercli.containers.list(
649 filters={'status': 'running', 'name': 'prometheus'})
stevenvanrossema7f601c2017-02-09 13:54:42 +0100650 if len(list1) >= 1:
stevenvanrossem33d76892017-02-13 00:13:37 +0100651 time.sleep(1)
stevenvanrossema7f601c2017-02-09 13:54:42 +0100652 started = True
653 if wait_time > 5:
654 return 'skewmon not started'
655 time.sleep(1)
656 wait_time += 1
stevenvanrossemfb8c8392017-01-28 17:29:11 +0100657 return ret
658
stevenvanrossemc63c5492017-05-08 16:10:13 +0200659 def term(self, vnf_list=[]):
660 """
661 Start a terminal window for the specified VNFs
662 (start a terminal for all VNFs if vnf_list is empty)
663 :param vnf_list:
664 :return:
665 """
666
stevenvanrossemc63c5492017-05-08 16:10:13 +0200667 if vnf_list is None:
668 vnf_list = []
669 if not isinstance(vnf_list, list):
670 vnf_list = str(vnf_list).split(',')
671 vnf_list = map(str.strip, vnf_list)
672 logging.info('vnf_list: {}'.format(vnf_list))
673
674 return self.start_xterm(vnf_list)
675
stevenvanrossemc63c5492017-05-08 16:10:13 +0200676 # start an xterm for the specfified vnfs
peusterm72f09882018-05-15 17:10:27 +0200677
stevenvanrossemc63c5492017-05-08 16:10:13 +0200678 def start_xterm(self, vnf_names):
679 # start xterm for all vnfs
680 for vnf_name in vnf_names:
681 terminal_cmd = "docker exec -it mn.{0} /bin/bash".format(vnf_name)
682
683 cmd = ['xterm', '-xrm', 'XTerm*selectToClipboard: true', '-xrm', 'XTerm.vt100.allowTitleOps: false',
684 '-T', vnf_name,
685 '-e', terminal_cmd]
686 Popen(cmd)
687
688 ret = 'xterms started for {0}'.format(vnf_names)
689 if len(vnf_names) == 0:
690 ret = 'vnf list is empty, no xterms started'
691 return ret