blob: ba04771c5107d092700c570460dd2791c204b7e4 [file] [log] [blame]
peusterm79ef6ae2016-07-08 13:53:57 +02001"""
2Copyright (c) 2015 SONATA-NFV
3ALL RIGHTS RESERVED.
4
5Licensed under the Apache License, Version 2.0 (the "License");
6you may not use this file except in compliance with the License.
7You may obtain a copy of the License at
8
9 http://www.apache.org/licenses/LICENSE-2.0
10
11Unless required by applicable law or agreed to in writing, software
12distributed under the License is distributed on an "AS IS" BASIS,
13WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14See the License for the specific language governing permissions and
15limitations under the License.
16
17Neither the name of the SONATA-NFV [, ANY ADDITIONAL AFFILIATION]
18nor the names of its contributors may be used to endorse or promote
19products derived from this software without specific prior written
20permission.
21
22This work has been performed in the framework of the SONATA project,
23funded by the European Commission under Grant number 671517 through
24the Horizon 2020 and 5G-PPP programmes. The authors would like to
25acknowledge the contributions of their colleagues of the SONATA
26partner consortium (www.sonata-nfv.eu).
27"""
stevenvanrossemc5a536a2016-02-16 14:52:39 +010028
stevenvanrossemc5a536a2016-02-16 14:52:39 +010029import logging
stevenvanrossem51d4ae72016-08-10 13:22:53 +020030import sys
stevenvanrossemc5a536a2016-02-16 14:52:39 +010031from mininet.node import OVSSwitch
32import ast
stevenvanrossem9315da42016-04-11 12:10:06 +020033import time
stevenvanrossem300e1e52016-04-22 22:17:51 +020034from prometheus_client import start_http_server, Summary, Histogram, Gauge, Counter, REGISTRY, CollectorRegistry, \
35 pushadd_to_gateway, push_to_gateway, delete_from_gateway
stevenvanrossema24b4372016-04-14 09:55:20 +020036import threading
stevenvanrosseme131bf52016-07-14 11:42:09 +020037from subprocess import Popen
stevenvanrossemb098cb52016-04-15 13:28:23 +020038import os
stevenvanrossema24b4372016-04-14 09:55:20 +020039
stevenvanrossem461941c2016-05-10 11:41:29 +020040
stevenvanrossemc5a536a2016-02-16 14:52:39 +010041logging.basicConfig(level=logging.INFO)
42
43"""
stevenvanrossema24b4372016-04-14 09:55:20 +020044class to read openflow stats from the Ryu controller of the DCNetwork
stevenvanrossemc5a536a2016-02-16 14:52:39 +010045"""
46
47class DCNetworkMonitor():
48 def __init__(self, net):
49 self.net = net
stevenvanrossem27b6d952016-05-10 16:37:57 +020050
stevenvanrosseme131bf52016-07-14 11:42:09 +020051 # TODO: these global variables should be part of a config file?
52 '''
53 # prometheus is started outside of son-emu
stevenvanrossem48db0512016-05-18 15:43:24 +020054 prometheus_ip = '127.0.0.1'
stevenvanrossem461941c2016-05-10 11:41:29 +020055 prometheus_port = '9090'
56 self.prometheus_REST_api = 'http://{0}:{1}'.format(prometheus_ip, prometheus_port)
stevenvanrosseme131bf52016-07-14 11:42:09 +020057 '''
stevenvanrossema24b4372016-04-14 09:55:20 +020058 # helper variables to calculate the metrics
stevenvanrosseme131bf52016-07-14 11:42:09 +020059 # pushgateway is started outside of son-emu and son-emu is started with net=host
60 # so localhost:9091 works
stevenvanrossem300e1e52016-04-22 22:17:51 +020061 self.pushgateway = 'localhost:9091'
stevenvanrosseme131bf52016-07-14 11:42:09 +020062 # when sdk is started with docker-compose, we could use
63 # self.pushgateway = 'pushgateway:9091'
stevenvanrosseme131bf52016-07-14 11:42:09 +020064
stevenvanrossema24b4372016-04-14 09:55:20 +020065 # supported Prometheus metrics
stevenvanrossem300e1e52016-04-22 22:17:51 +020066 self.registry = CollectorRegistry()
stevenvanrossema24b4372016-04-14 09:55:20 +020067 self.prom_tx_packet_count = Gauge('sonemu_tx_count_packets', 'Total number of packets sent',
stevenvanrossem461941c2016-05-10 11:41:29 +020068 ['vnf_name', 'vnf_interface', 'flow_id'], registry=self.registry)
stevenvanrossema24b4372016-04-14 09:55:20 +020069 self.prom_rx_packet_count = Gauge('sonemu_rx_count_packets', 'Total number of packets received',
stevenvanrossem461941c2016-05-10 11:41:29 +020070 ['vnf_name', 'vnf_interface', 'flow_id'], registry=self.registry)
stevenvanrossema24b4372016-04-14 09:55:20 +020071 self.prom_tx_byte_count = Gauge('sonemu_tx_count_bytes', 'Total number of bytes sent',
stevenvanrossem461941c2016-05-10 11:41:29 +020072 ['vnf_name', 'vnf_interface', 'flow_id'], registry=self.registry)
stevenvanrossema24b4372016-04-14 09:55:20 +020073 self.prom_rx_byte_count = Gauge('sonemu_rx_count_bytes', 'Total number of bytes received',
stevenvanrossem461941c2016-05-10 11:41:29 +020074 ['vnf_name', 'vnf_interface', 'flow_id'], registry=self.registry)
stevenvanrossema24b4372016-04-14 09:55:20 +020075
76 self.prom_metrics={'tx_packets':self.prom_tx_packet_count, 'rx_packets':self.prom_rx_packet_count,
77 'tx_bytes':self.prom_tx_byte_count,'rx_bytes':self.prom_rx_byte_count}
78
79 # list of installed metrics to monitor
80 # each entry can contain this data
81 '''
82 {
83 switch_dpid = 0
84 vnf_name = None
85 vnf_interface = None
86 previous_measurement = 0
87 previous_monitor_time = 0
88 metric_key = None
89 mon_port = None
90 }
91 '''
stevenvanrossem300e1e52016-04-22 22:17:51 +020092 self.monitor_lock = threading.Lock()
stevenvanrossem461941c2016-05-10 11:41:29 +020093 self.monitor_flow_lock = threading.Lock()
stevenvanrossemc6abf132016-04-14 11:15:58 +020094 self.network_metrics = []
stevenvanrossem461941c2016-05-10 11:41:29 +020095 self.flow_metrics = []
stevenvanrossema24b4372016-04-14 09:55:20 +020096
97 # start monitoring thread
stevenvanrossemb098cb52016-04-15 13:28:23 +020098 self.start_monitoring = True
stevenvanrossema24b4372016-04-14 09:55:20 +020099 self.monitor_thread = threading.Thread(target=self.get_network_metrics)
100 self.monitor_thread.start()
101
stevenvanrossem461941c2016-05-10 11:41:29 +0200102 self.monitor_flow_thread = threading.Thread(target=self.get_flow_metrics)
103 self.monitor_flow_thread.start()
104
stevenvanrossemc6abf132016-04-14 11:15:58 +0200105 # helper tools
stevenvanrossem89706802016-07-19 02:54:45 +0200106 # cAdvisor, Prometheus pushgateway and DB are started as external container, outside of son-emu
107
stevenvanrossema24b4372016-04-14 09:55:20 +0200108
stevenvanrossemed711fd2016-04-11 16:59:29 +0200109 # first set some parameters, before measurement can start
stevenvanrossem461941c2016-05-10 11:41:29 +0200110 def setup_flow(self, vnf_name, vnf_interface=None, metric='tx_packets', cookie=0):
111
112 flow_metric = {}
113
114 # check if port is specified (vnf:port)
115 if vnf_interface is None:
116 # take first interface by default
117 connected_sw = self.net.DCNetwork_graph.neighbors(vnf_name)[0]
118 link_dict = self.net.DCNetwork_graph[vnf_name][connected_sw]
119 vnf_interface = link_dict[0]['src_port_id']
120
121 flow_metric['vnf_name'] = vnf_name
122 flow_metric['vnf_interface'] = vnf_interface
123
124 vnf_switch = None
125 for connected_sw in self.net.DCNetwork_graph.neighbors(vnf_name):
126 link_dict = self.net.DCNetwork_graph[vnf_name][connected_sw]
127 for link in link_dict:
stevenvanrossem461941c2016-05-10 11:41:29 +0200128 if link_dict[link]['src_port_id'] == vnf_interface:
129 # found the right link and connected switch
stevenvanrossem461941c2016-05-10 11:41:29 +0200130 vnf_switch = connected_sw
131 flow_metric['mon_port'] = link_dict[link]['dst_port_nr']
132 break
133
134 if not vnf_switch:
135 logging.exception("vnf switch of {0}:{1} not found!".format(vnf_name, vnf_interface))
136 return "vnf switch of {0}:{1} not found!".format(vnf_name, vnf_interface)
137
138 try:
139 # default port direction to monitor
140 if metric is None:
141 metric = 'tx_packets'
142
143 next_node = self.net.getNodeByName(vnf_switch)
144
145 if not isinstance(next_node, OVSSwitch):
146 logging.info("vnf: {0} is not connected to switch".format(vnf_name))
147 return
148
149 flow_metric['previous_measurement'] = 0
150 flow_metric['previous_monitor_time'] = 0
151
152 flow_metric['switch_dpid'] = int(str(next_node.dpid), 16)
153 flow_metric['metric_key'] = metric
154 flow_metric['cookie'] = cookie
155
156 self.monitor_flow_lock.acquire()
157 self.flow_metrics.append(flow_metric)
158 self.monitor_flow_lock.release()
159
160 logging.info('Started monitoring flow:{3} {2} on {0}:{1}'.format(vnf_name, vnf_interface, metric, cookie))
161 return 'Started monitoring flow:{3} {2} on {0}:{1}'.format(vnf_name, vnf_interface, metric, cookie)
162
163 except Exception as ex:
164 logging.exception("setup_metric error.")
165 return ex.message
166
stevenvanrossem9c8a4122016-07-16 03:23:13 +0200167 def stop_flow(self, vnf_name, vnf_interface=None, metric=None, cookie=0,):
168
169 # check if port is specified (vnf:port)
170 if vnf_interface is None and metric is not None:
171 # take first interface by default
172 connected_sw = self.net.DCNetwork_graph.neighbors(vnf_name)[0]
173 link_dict = self.net.DCNetwork_graph[vnf_name][connected_sw]
174 vnf_interface = link_dict[0]['src_port_id']
175
stevenvanrossem1ef77022016-05-12 16:36:10 +0200176 for flow_dict in self.flow_metrics:
177 if flow_dict['vnf_name'] == vnf_name and flow_dict['vnf_interface'] == vnf_interface \
178 and flow_dict['metric_key'] == metric and flow_dict['cookie'] == cookie:
179
180 self.monitor_flow_lock.acquire()
181
182 self.flow_metrics.remove(flow_dict)
183
184 for collector in self.registry._collectors:
185 if (vnf_name, vnf_interface, cookie) in collector._metrics:
stevenvanrossem1ef77022016-05-12 16:36:10 +0200186 collector.remove(vnf_name, vnf_interface, cookie)
187
188 delete_from_gateway(self.pushgateway, job='sonemu-SDNcontroller')
189
190 self.monitor_flow_lock.release()
191
192 logging.info('Stopped monitoring flow {3}: {2} on {0}:{1}'.format(vnf_name, vnf_interface, metric, cookie))
193 return 'Stopped monitoring flow {3}: {2} on {0}:{1}'.format(vnf_name, vnf_interface, metric, cookie)
194
stevenvanrossem9c8a4122016-07-16 03:23:13 +0200195 return 'Error stopping monitoring flow: {0} on {1}:{2}'.format(metric, vnf_name, vnf_interface)
196
stevenvanrossem461941c2016-05-10 11:41:29 +0200197
198 # first set some parameters, before measurement can start
stevenvanrossema24b4372016-04-14 09:55:20 +0200199 def setup_metric(self, vnf_name, vnf_interface=None, metric='tx_packets'):
200
201 network_metric = {}
202
stevenvanrossem9315da42016-04-11 12:10:06 +0200203 # check if port is specified (vnf:port)
stevenvanrossemed711fd2016-04-11 16:59:29 +0200204 if vnf_interface is None:
stevenvanrossem9315da42016-04-11 12:10:06 +0200205 # take first interface by default
206 connected_sw = self.net.DCNetwork_graph.neighbors(vnf_name)[0]
207 link_dict = self.net.DCNetwork_graph[vnf_name][connected_sw]
208 vnf_interface = link_dict[0]['src_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200209
stevenvanrossema24b4372016-04-14 09:55:20 +0200210 network_metric['vnf_name'] = vnf_name
211 network_metric['vnf_interface'] = vnf_interface
stevenvanrossema24b4372016-04-14 09:55:20 +0200212
stevenvanrossem9315da42016-04-11 12:10:06 +0200213 for connected_sw in self.net.DCNetwork_graph.neighbors(vnf_name):
214 link_dict = self.net.DCNetwork_graph[vnf_name][connected_sw]
215 for link in link_dict:
stevenvanrossem9315da42016-04-11 12:10:06 +0200216 if link_dict[link]['src_port_id'] == vnf_interface:
217 # found the right link and connected switch
stevenvanrossem307aa1f2016-05-06 10:35:15 +0200218 network_metric['mon_port'] = link_dict[link]['dst_port_nr']
stevenvanrossem9315da42016-04-11 12:10:06 +0200219 break
220
stevenvanrossema24b4372016-04-14 09:55:20 +0200221 if 'mon_port' not in network_metric:
222 logging.exception("vnf interface {0}:{1} not found!".format(vnf_name,vnf_interface))
223 return "vnf interface {0}:{1} not found!".format(vnf_name,vnf_interface)
224
stevenvanrossem9315da42016-04-11 12:10:06 +0200225 try:
226 # default port direction to monitor
stevenvanrossemed711fd2016-04-11 16:59:29 +0200227 if metric is None:
stevenvanrossema24b4372016-04-14 09:55:20 +0200228 metric = 'tx_packets'
stevenvanrossem9315da42016-04-11 12:10:06 +0200229
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100230 vnf_switch = self.net.DCNetwork_graph.neighbors(str(vnf_name))
231
232 if len(vnf_switch) > 1:
233 logging.info("vnf: {0} has multiple ports".format(vnf_name))
234 return
235 elif len(vnf_switch) == 0:
236 logging.info("vnf: {0} is not connected".format(vnf_name))
237 return
238 else:
239 vnf_switch = vnf_switch[0]
240 next_node = self.net.getNodeByName(vnf_switch)
241
stevenvanrossemed711fd2016-04-11 16:59:29 +0200242 if not isinstance(next_node, OVSSwitch):
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100243 logging.info("vnf: {0} is not connected to switch".format(vnf_name))
244 return
245
stevenvanrossema24b4372016-04-14 09:55:20 +0200246 network_metric['previous_measurement'] = 0
247 network_metric['previous_monitor_time'] = 0
stevenvanrossemb098cb52016-04-15 13:28:23 +0200248
stevenvanrossem9315da42016-04-11 12:10:06 +0200249
stevenvanrossema24b4372016-04-14 09:55:20 +0200250 network_metric['switch_dpid'] = int(str(next_node.dpid), 16)
251 network_metric['metric_key'] = metric
stevenvanrossemb098cb52016-04-15 13:28:23 +0200252
stevenvanrossem300e1e52016-04-22 22:17:51 +0200253 self.monitor_lock.acquire()
254
stevenvanrossema24b4372016-04-14 09:55:20 +0200255 self.network_metrics.append(network_metric)
stevenvanrossem300e1e52016-04-22 22:17:51 +0200256 self.monitor_lock.release()
257
stevenvanrossema24b4372016-04-14 09:55:20 +0200258
259 logging.info('Started monitoring: {2} on {0}:{1}'.format(vnf_name, vnf_interface, metric))
260 return 'Started monitoring: {2} on {0}:{1}'.format(vnf_name, vnf_interface, metric)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100261
stevenvanrossemed711fd2016-04-11 16:59:29 +0200262 except Exception as ex:
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200263 logging.exception("setup_metric error.")
stevenvanrossemed711fd2016-04-11 16:59:29 +0200264 return ex.message
stevenvanrossem9315da42016-04-11 12:10:06 +0200265
stevenvanrossem461941c2016-05-10 11:41:29 +0200266 def stop_metric(self, vnf_name, vnf_interface=None, metric=None):
stevenvanrossem300e1e52016-04-22 22:17:51 +0200267
stevenvanrossem9c8a4122016-07-16 03:23:13 +0200268 # check if port is specified (vnf:port)
269 if vnf_interface is None and metric is not None:
270 # take first interface by default
271 connected_sw = self.net.DCNetwork_graph.neighbors(vnf_name)[0]
272 link_dict = self.net.DCNetwork_graph[vnf_name][connected_sw]
273 vnf_interface = link_dict[0]['src_port_id']
274
stevenvanrossemb098cb52016-04-15 13:28:23 +0200275 for metric_dict in self.network_metrics:
276 if metric_dict['vnf_name'] == vnf_name and metric_dict['vnf_interface'] == vnf_interface \
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200277 and metric_dict['metric_key'] == metric:
278
stevenvanrossem300e1e52016-04-22 22:17:51 +0200279 self.monitor_lock.acquire()
280
stevenvanrossemb098cb52016-04-15 13:28:23 +0200281 self.network_metrics.remove(metric_dict)
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200282
283 #this removes the complete metric, all labels...
284 #REGISTRY.unregister(self.prom_metrics[metric_dict['metric_key']])
stevenvanrossem300e1e52016-04-22 22:17:51 +0200285 #self.registry.unregister(self.prom_metrics[metric_dict['metric_key']])
286
287 for collector in self.registry._collectors :
stevenvanrosseme131bf52016-07-14 11:42:09 +0200288
stevenvanrossem300e1e52016-04-22 22:17:51 +0200289 """
290 INFO:root:name:sonemu_rx_count_packets
291 labels:('vnf_name', 'vnf_interface')
292 metrics:{(u'tsrc', u'output'): < prometheus_client.core.Gauge
293 object
294 at
295 0x7f353447fd10 >}
296 """
297 logging.info('{0}'.format(collector._metrics.values()))
stevenvanrosseme131bf52016-07-14 11:42:09 +0200298
stevenvanrossem461941c2016-05-10 11:41:29 +0200299 if (vnf_name, vnf_interface, 'None') in collector._metrics:
stevenvanrossem300e1e52016-04-22 22:17:51 +0200300 logging.info('2 name:{0} labels:{1} metrics:{2}'.format(collector._name, collector._labelnames,
301 collector._metrics))
stevenvanrossem461941c2016-05-10 11:41:29 +0200302 collector.remove(vnf_name, vnf_interface, 'None')
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200303
304 # set values to NaN, prometheus api currently does not support removal of metrics
stevenvanrossem300e1e52016-04-22 22:17:51 +0200305 #self.prom_metrics[metric_dict['metric_key']].labels(vnf_name, vnf_interface).set(float('nan'))
306
307 # this removes the complete metric, all labels...
308 # 1 single monitor job for all metrics of the SDN controller
309 # we can only remove from the pushgateway grouping keys(labels) which we have defined for the add_to_pushgateway
310 # we can not specify labels from the metrics to be removed
311 # if we need to remove the metrics seperatelty, we need to give them a separate grouping key, and probably a diffferent registry also
312 delete_from_gateway(self.pushgateway, job='sonemu-SDNcontroller')
313
314 self.monitor_lock.release()
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200315
stevenvanrossemb098cb52016-04-15 13:28:23 +0200316 logging.info('Stopped monitoring: {2} on {0}:{1}'.format(vnf_name, vnf_interface, metric))
317 return 'Stopped monitoring: {2} on {0}:{1}'.format(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)
323 for collector in self.registry._collectors:
324 collector_dict = collector._metrics.copy()
325 for name, interface, id in collector_dict:
326 if name == vnf_name:
327 logging.info('3 name:{0} labels:{1} metrics:{2}'.format(collector._name, collector._labelnames,
328 collector._metrics))
329 collector.remove(name, interface, 'None')
330
331 delete_from_gateway(self.pushgateway, job='sonemu-SDNcontroller')
332 self.monitor_lock.release()
333 logging.info('Stopped monitoring vnf: {0}'.format(vnf_name))
334 return 'Stopped monitoring: {0}'.format(vnf_name)
335
stevenvanrossem9c8a4122016-07-16 03:23:13 +0200336 return 'Error stopping monitoring metric: {0} on {1}:{2}'.format(metric, vnf_name, vnf_interface)
stevenvanrossemb098cb52016-04-15 13:28:23 +0200337
stevenvanrossem9c8a4122016-07-16 03:23:13 +0200338
339
340
341
342# get all metrics defined in the list and export it to Prometheus
stevenvanrossem461941c2016-05-10 11:41:29 +0200343 def get_flow_metrics(self):
344 while self.start_monitoring:
345
346 self.monitor_flow_lock.acquire()
347
348 for flow_dict in self.flow_metrics:
349 data = {}
350
351 data['cookie'] = flow_dict['cookie']
stevenvanrossem1a5ced92016-08-07 00:52:13 +0200352 data['cookie_mask'] = flow_dict['cookie']
stevenvanrossem461941c2016-05-10 11:41:29 +0200353
354 if 'tx' in flow_dict['metric_key']:
355 data['match'] = {'in_port':flow_dict['mon_port']}
356 elif 'rx' in flow_dict['metric_key']:
357 data['out_port'] = flow_dict['mon_port']
358
359
360 # query Ryu
stevenvanrossem27b6d952016-05-10 16:37:57 +0200361 ret = self.net.ryu_REST('stats/flow', dpid=flow_dict['switch_dpid'], data=data)
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200362 if isinstance(ret, dict):
363 flow_stat_dict = ret
364 elif isinstance(ret, basestring):
365 flow_stat_dict = ast.literal_eval(ret.rstrip())
366 else:
367 flow_stat_dict = None
368
stevenvanrosseme131bf52016-07-14 11:42:09 +0200369 logging.debug('received flow stat:{0} '.format(flow_stat_dict))
stevenvanrossem3fc13932016-08-09 23:39:16 +0200370
stevenvanrossem461941c2016-05-10 11:41:29 +0200371 self.set_flow_metric(flow_dict, flow_stat_dict)
372
373 self.monitor_flow_lock.release()
374 time.sleep(1)
375
stevenvanrossema24b4372016-04-14 09:55:20 +0200376 def get_network_metrics(self):
stevenvanrossemb098cb52016-04-15 13:28:23 +0200377 while self.start_monitoring:
stevenvanrossem300e1e52016-04-22 22:17:51 +0200378
379 self.monitor_lock.acquire()
380
stevenvanrossema24b4372016-04-14 09:55:20 +0200381 # group metrics by dpid to optimize the rest api calls
382 dpid_list = [metric_dict['switch_dpid'] for metric_dict in self.network_metrics]
383 dpid_set = set(dpid_list)
384
385 for dpid in dpid_set:
386
387 # query Ryu
stevenvanrossem27b6d952016-05-10 16:37:57 +0200388 ret = self.net.ryu_REST('stats/port', dpid=dpid)
stevenvanrossema24b4372016-04-14 09:55:20 +0200389 port_stat_dict = ast.literal_eval(ret)
390
391 metric_list = [metric_dict for metric_dict in self.network_metrics
392 if int(metric_dict['switch_dpid'])==int(dpid)]
stevenvanrosseme131bf52016-07-14 11:42:09 +0200393
stevenvanrossema24b4372016-04-14 09:55:20 +0200394 for metric_dict in metric_list:
395 self.set_network_metric(metric_dict, port_stat_dict)
396
stevenvanrossem300e1e52016-04-22 22:17:51 +0200397 self.monitor_lock.release()
stevenvanrossema24b4372016-04-14 09:55:20 +0200398 time.sleep(1)
399
stevenvanrossemb098cb52016-04-15 13:28:23 +0200400 # add metric to the list to export to Prometheus, parse the Ryu port-stats reply
stevenvanrossema24b4372016-04-14 09:55:20 +0200401 def set_network_metric(self, metric_dict, port_stat_dict):
stevenvanrossemb098cb52016-04-15 13:28:23 +0200402 # vnf tx is the datacenter switch rx and vice-versa
403 metric_key = self.switch_tx_rx(metric_dict['metric_key'])
stevenvanrossema24b4372016-04-14 09:55:20 +0200404 switch_dpid = metric_dict['switch_dpid']
405 vnf_name = metric_dict['vnf_name']
406 vnf_interface = metric_dict['vnf_interface']
407 previous_measurement = metric_dict['previous_measurement']
408 previous_monitor_time = metric_dict['previous_monitor_time']
409 mon_port = metric_dict['mon_port']
410
411 for port_stat in port_stat_dict[str(switch_dpid)]:
412 if int(port_stat['port_no']) == int(mon_port):
413 port_uptime = port_stat['duration_sec'] + port_stat['duration_nsec'] * 10 ** (-9)
414 this_measurement = int(port_stat[metric_key])
stevenvanrossema24b4372016-04-14 09:55:20 +0200415
416 # set prometheus metric
stevenvanrossem300e1e52016-04-22 22:17:51 +0200417 self.prom_metrics[metric_dict['metric_key']].\
stevenvanrossem461941c2016-05-10 11:41:29 +0200418 labels({'vnf_name': vnf_name, 'vnf_interface': vnf_interface, 'flow_id': None}).\
stevenvanrossem300e1e52016-04-22 22:17:51 +0200419 set(this_measurement)
stevenvanrossem300e1e52016-04-22 22:17:51 +0200420
421 # 1 single monitor job for all metrics of the SDN controller
422 pushadd_to_gateway(self.pushgateway, job='sonemu-SDNcontroller', registry=self.registry)
stevenvanrossema24b4372016-04-14 09:55:20 +0200423
stevenvanrosseme131bf52016-07-14 11:42:09 +0200424 # also the rate is calculated here, but not used for now
425 # (rate can be easily queried from prometheus also)
stevenvanrossema24b4372016-04-14 09:55:20 +0200426 if previous_monitor_time <= 0 or previous_monitor_time >= port_uptime:
427 metric_dict['previous_measurement'] = int(port_stat[metric_key])
428 metric_dict['previous_monitor_time'] = port_uptime
429 # do first measurement
stevenvanrossema24b4372016-04-14 09:55:20 +0200430 time.sleep(1)
stevenvanrossem300e1e52016-04-22 22:17:51 +0200431 self.monitor_lock.release()
stevenvanrossem61fd5282016-04-29 12:41:54 +0200432
stevenvanrossem300e1e52016-04-22 22:17:51 +0200433 metric_rate = self.get_network_metrics()
434 return metric_rate
stevenvanrossem61fd5282016-04-29 12:41:54 +0200435
stevenvanrossema24b4372016-04-14 09:55:20 +0200436 else:
437 time_delta = (port_uptime - metric_dict['previous_monitor_time'])
stevenvanrossem300e1e52016-04-22 22:17:51 +0200438 metric_rate = (this_measurement - metric_dict['previous_measurement']) / float(time_delta)
stevenvanrossema24b4372016-04-14 09:55:20 +0200439
440 metric_dict['previous_measurement'] = this_measurement
441 metric_dict['previous_monitor_time'] = port_uptime
stevenvanrossem300e1e52016-04-22 22:17:51 +0200442 return metric_rate
stevenvanrossema24b4372016-04-14 09:55:20 +0200443
444 logging.exception('metric {0} not found on {1}:{2}'.format(metric_key, vnf_name, vnf_interface))
445 return 'metric {0} not found on {1}:{2}'.format(metric_key, vnf_name, vnf_interface)
446
stevenvanrossem461941c2016-05-10 11:41:29 +0200447 def set_flow_metric(self, metric_dict, flow_stat_dict):
448 # vnf tx is the datacenter switch rx and vice-versa
stevenvanrossem461941c2016-05-10 11:41:29 +0200449 metric_key = metric_dict['metric_key']
450 switch_dpid = metric_dict['switch_dpid']
451 vnf_name = metric_dict['vnf_name']
452 vnf_interface = metric_dict['vnf_interface']
453 previous_measurement = metric_dict['previous_measurement']
454 previous_monitor_time = metric_dict['previous_monitor_time']
455 cookie = metric_dict['cookie']
stevenvanrossema24b4372016-04-14 09:55:20 +0200456
stevenvanrossem9c8a4122016-07-16 03:23:13 +0200457 counter = 0
458 for flow_stat in flow_stat_dict[str(switch_dpid)]:
459 if 'bytes' in metric_key:
460 counter += flow_stat['byte_count']
461 elif 'packet' in metric_key:
462 counter += flow_stat['packet_count']
463
464 flow_stat = flow_stat_dict[str(switch_dpid)][0]
stevenvanrossem461941c2016-05-10 11:41:29 +0200465 flow_uptime = flow_stat['duration_sec'] + flow_stat['duration_nsec'] * 10 ** (-9)
466
467 self.prom_metrics[metric_dict['metric_key']]. \
468 labels({'vnf_name': vnf_name, 'vnf_interface': vnf_interface, 'flow_id': cookie}). \
469 set(counter)
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200470 try:
471 pushadd_to_gateway(self.pushgateway, job='sonemu-SDNcontroller', registry=self.registry)
472 except Exception, e:
473 logging.warning("Pushgateway not reachable: {0} {1}".format(Exception, e))
stevenvanrossem461941c2016-05-10 11:41:29 +0200474
stevenvanrossemc6abf132016-04-14 11:15:58 +0200475
476 def start_Prometheus(self, port=9090):
stevenvanrossemb098cb52016-04-15 13:28:23 +0200477 # prometheus.yml configuration file is located in the same directory as this file
stevenvanrossemc6abf132016-04-14 11:15:58 +0200478 cmd = ["docker",
479 "run",
480 "--rm",
481 "-p", "{0}:9090".format(port),
stevenvanrossemb098cb52016-04-15 13:28:23 +0200482 "-v", "{0}/prometheus.yml:/etc/prometheus/prometheus.yml".format(os.path.dirname(os.path.abspath(__file__))),
stevenvanrossem61fd5282016-04-29 12:41:54 +0200483 "-v", "{0}/profile.rules:/etc/prometheus/profile.rules".format(os.path.dirname(os.path.abspath(__file__))),
stevenvanrossemc6abf132016-04-14 11:15:58 +0200484 "--name", "prometheus",
485 "prom/prometheus"
486 ]
stevenvanrossemb098cb52016-04-15 13:28:23 +0200487 logging.info('Start Prometheus container {0}'.format(cmd))
488 return Popen(cmd)
stevenvanrossemc6abf132016-04-14 11:15:58 +0200489
stevenvanrossemadfd06f2016-04-22 10:39:08 +0200490 def start_PushGateway(self, port=9091):
491 cmd = ["docker",
492 "run",
493 "-d",
494 "-p", "{0}:9091".format(port),
495 "--name", "pushgateway",
496 "prom/pushgateway"
497 ]
498
499 logging.info('Start Prometheus Push Gateway container {0}'.format(cmd))
500 return Popen(cmd)
501
stevenvanrossemb098cb52016-04-15 13:28:23 +0200502 def start_cadvisor(self, port=8090):
stevenvanrossemc6abf132016-04-14 11:15:58 +0200503 cmd = ["docker",
504 "run",
505 "--rm",
506 "--volume=/:/rootfs:ro",
507 "--volume=/var/run:/var/run:rw",
508 "--volume=/sys:/sys:ro",
509 "--volume=/var/lib/docker/:/var/lib/docker:ro",
510 "--publish={0}:8080".format(port),
511 "--name=cadvisor",
512 "google/cadvisor:latest"
513 ]
stevenvanrossemb098cb52016-04-15 13:28:23 +0200514 logging.info('Start cAdvisor container {0}'.format(cmd))
515 return Popen(cmd)
stevenvanrossemc6abf132016-04-14 11:15:58 +0200516
517 def stop(self):
stevenvanrossemb098cb52016-04-15 13:28:23 +0200518 # stop the monitoring thread
519 self.start_monitoring = False
520 self.monitor_thread.join()
stevenvanrossem461941c2016-05-10 11:41:29 +0200521 self.monitor_flow_thread.join()
stevenvanrossemb098cb52016-04-15 13:28:23 +0200522
stevenvanrossem9c8a4122016-07-16 03:23:13 +0200523 # these containers are used for monitoring but are started now outside of son-emu
stevenvanrossem2fdfbf42016-05-13 15:08:47 +0200524 '''
stevenvanrossemc6abf132016-04-14 11:15:58 +0200525 if self.prometheus_process is not None:
stevenvanrossemb098cb52016-04-15 13:28:23 +0200526 logging.info('stopping prometheus container')
stevenvanrossemc6abf132016-04-14 11:15:58 +0200527 self.prometheus_process.terminate()
528 self.prometheus_process.kill()
stevenvanrossemb098cb52016-04-15 13:28:23 +0200529 self._stop_container('prometheus')
stevenvanrossemc6abf132016-04-14 11:15:58 +0200530
stevenvanrossemadfd06f2016-04-22 10:39:08 +0200531 if self.pushgateway_process is not None:
532 logging.info('stopping pushgateway container')
533 self.pushgateway_process.terminate()
534 self.pushgateway_process.kill()
535 self._stop_container('pushgateway')
536
stevenvanrossemb098cb52016-04-15 13:28:23 +0200537 if self.cadvisor_process is not None:
538 logging.info('stopping cadvisor container')
539 self.cadvisor_process.terminate()
540 self.cadvisor_process.kill()
541 self._stop_container('cadvisor')
stevenvanrossem9c8a4122016-07-16 03:23:13 +0200542 '''
stevenvanrossemb098cb52016-04-15 13:28:23 +0200543
544 def switch_tx_rx(self,metric=''):
545 # when monitoring vnfs, the tx of the datacenter switch is actually the rx of the vnf
546 # so we need to change the metric name to be consistent with the vnf rx or tx
547 if 'tx' in metric:
548 metric = metric.replace('tx','rx')
549 elif 'rx' in metric:
550 metric = metric.replace('rx','tx')
551
552 return metric
553
554 def _stop_container(self, name):
555 cmd = ["docker",
556 "stop",
557 name]
558 Popen(cmd).wait()
559
560 cmd = ["docker",
561 "rm",
562 name]
563 Popen(cmd).wait()
564