blob: 21985cba695edd456d9847c16626ae49517bb220 [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
stevenvanrossemc63c5492017-05-08 16:10:13 +020037from subprocess import Popen
stevenvanrossemb098cb52016-04-15 13:28:23 +020038import os
stevenvanrossembeba14d2017-01-28 15:40:30 +010039import docker
stevenvanrossemfb8c8392017-01-28 17:29:11 +010040import json
stevenvanrossem5d61e262017-05-07 22:40:44 +020041from copy import deepcopy
stevenvanrossem461941c2016-05-10 11:41:29 +020042
peusterm5b428742017-06-16 10:08:11 +020043logging.basicConfig()
stevenvanrossemc5a536a2016-02-16 14:52:39 +010044
45"""
stevenvanrossema24b4372016-04-14 09:55:20 +020046class to read openflow stats from the Ryu controller of the DCNetwork
stevenvanrossemc5a536a2016-02-16 14:52:39 +010047"""
48
stevenvanrossema5aeb372016-08-18 17:32:24 +020049PUSHGATEWAY_PORT = 9091
stevenvanrosseme50b0a72016-08-18 17:42:50 +020050# we cannot use port 8080 because ryu-ofrest api is already using that one
51CADVISOR_PORT = 8081
stevenvanrossema5aeb372016-08-18 17:32:24 +020052
stevenvanrossemb7715d32016-08-26 16:22:21 +020053COOKIE_MASK = 0xffffffff
54
stevenvanrossemc5a536a2016-02-16 14:52:39 +010055class DCNetworkMonitor():
56 def __init__(self, net):
57 self.net = net
stevenvanrossembeba14d2017-01-28 15:40:30 +010058 self.dockercli = docker.from_env()
stevenvanrossem27b6d952016-05-10 16:37:57 +020059
stevenvanrossema5aeb372016-08-18 17:32:24 +020060 # pushgateway address
61 self.pushgateway = 'localhost:{0}'.format(PUSHGATEWAY_PORT)
stevenvanrosseme131bf52016-07-14 11:42:09 +020062
stevenvanrossema24b4372016-04-14 09:55:20 +020063 # supported Prometheus metrics
stevenvanrossem300e1e52016-04-22 22:17:51 +020064 self.registry = CollectorRegistry()
stevenvanrossema24b4372016-04-14 09:55:20 +020065 self.prom_tx_packet_count = Gauge('sonemu_tx_count_packets', 'Total number of packets sent',
stevenvanrossem461941c2016-05-10 11:41:29 +020066 ['vnf_name', 'vnf_interface', 'flow_id'], registry=self.registry)
stevenvanrossema24b4372016-04-14 09:55:20 +020067 self.prom_rx_packet_count = Gauge('sonemu_rx_count_packets', 'Total number of packets received',
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_tx_byte_count = Gauge('sonemu_tx_count_bytes', 'Total number of bytes sent',
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_rx_byte_count = Gauge('sonemu_rx_count_bytes', 'Total number of bytes received',
stevenvanrossem461941c2016-05-10 11:41:29 +020072 ['vnf_name', 'vnf_interface', 'flow_id'], registry=self.registry)
stevenvanrossema24b4372016-04-14 09:55:20 +020073
74 self.prom_metrics={'tx_packets':self.prom_tx_packet_count, 'rx_packets':self.prom_rx_packet_count,
75 'tx_bytes':self.prom_tx_byte_count,'rx_bytes':self.prom_rx_byte_count}
76
77 # list of installed metrics to monitor
78 # each entry can contain this data
79 '''
80 {
81 switch_dpid = 0
82 vnf_name = None
83 vnf_interface = None
84 previous_measurement = 0
85 previous_monitor_time = 0
86 metric_key = None
87 mon_port = None
88 }
89 '''
stevenvanrossem300e1e52016-04-22 22:17:51 +020090 self.monitor_lock = threading.Lock()
stevenvanrossem461941c2016-05-10 11:41:29 +020091 self.monitor_flow_lock = threading.Lock()
stevenvanrossemc6abf132016-04-14 11:15:58 +020092 self.network_metrics = []
stevenvanrossem461941c2016-05-10 11:41:29 +020093 self.flow_metrics = []
stevenvanrossemfb8c8392017-01-28 17:29:11 +010094 self.skewmon_metrics = {}
stevenvanrossema24b4372016-04-14 09:55:20 +020095
96 # start monitoring thread
stevenvanrossemb098cb52016-04-15 13:28:23 +020097 self.start_monitoring = True
stevenvanrossema24b4372016-04-14 09:55:20 +020098 self.monitor_thread = threading.Thread(target=self.get_network_metrics)
99 self.monitor_thread.start()
100
stevenvanrossem461941c2016-05-10 11:41:29 +0200101 self.monitor_flow_thread = threading.Thread(target=self.get_flow_metrics)
102 self.monitor_flow_thread.start()
103
stevenvanrossemc6abf132016-04-14 11:15:58 +0200104 # helper tools
stevenvanrossema5aeb372016-08-18 17:32:24 +0200105 # cAdvisor, Prometheus pushgateway are started as external container, to gather monitoring metric in son-emu
stevenvanrosseme50b0a72016-08-18 17:42:50 +0200106 self.pushgateway_process = self.start_PushGateway()
107 self.cadvisor_process = self.start_cAdvisor()
stevenvanrossem89706802016-07-19 02:54:45 +0200108
stevenvanrossema24b4372016-04-14 09:55:20 +0200109
stevenvanrossemed711fd2016-04-11 16:59:29 +0200110 # first set some parameters, before measurement can start
stevenvanrossem461941c2016-05-10 11:41:29 +0200111 def setup_flow(self, vnf_name, vnf_interface=None, metric='tx_packets', cookie=0):
112
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:
136 logging.exception("vnf switch of {0}:{1} not found!".format(vnf_name, vnf_interface))
137 return "vnf switch of {0}:{1} not found!".format(vnf_name, vnf_interface)
138
139 try:
140 # default port direction to monitor
141 if metric is None:
142 metric = 'tx_packets'
143
144 next_node = self.net.getNodeByName(vnf_switch)
145
146 if not isinstance(next_node, OVSSwitch):
147 logging.info("vnf: {0} is not connected to switch".format(vnf_name))
148 return
149
150 flow_metric['previous_measurement'] = 0
151 flow_metric['previous_monitor_time'] = 0
152
153 flow_metric['switch_dpid'] = int(str(next_node.dpid), 16)
154 flow_metric['metric_key'] = metric
155 flow_metric['cookie'] = cookie
156
157 self.monitor_flow_lock.acquire()
158 self.flow_metrics.append(flow_metric)
159 self.monitor_flow_lock.release()
160
161 logging.info('Started monitoring flow:{3} {2} on {0}:{1}'.format(vnf_name, vnf_interface, metric, cookie))
162 return 'Started monitoring flow:{3} {2} on {0}:{1}'.format(vnf_name, vnf_interface, metric, cookie)
163
164 except Exception as ex:
165 logging.exception("setup_metric error.")
166 return ex.message
167
stevenvanrossem9c8a4122016-07-16 03:23:13 +0200168 def stop_flow(self, vnf_name, vnf_interface=None, metric=None, cookie=0,):
169
170 # check if port is specified (vnf:port)
171 if vnf_interface is None and metric is not None:
172 # take first interface by default
173 connected_sw = self.net.DCNetwork_graph.neighbors(vnf_name)[0]
174 link_dict = self.net.DCNetwork_graph[vnf_name][connected_sw]
175 vnf_interface = link_dict[0]['src_port_id']
176
stevenvanrossem1ef77022016-05-12 16:36:10 +0200177 for flow_dict in self.flow_metrics:
178 if flow_dict['vnf_name'] == vnf_name and flow_dict['vnf_interface'] == vnf_interface \
179 and flow_dict['metric_key'] == metric and flow_dict['cookie'] == cookie:
180
181 self.monitor_flow_lock.acquire()
182
183 self.flow_metrics.remove(flow_dict)
184
stevenvanrossembeba14d2017-01-28 15:40:30 +0100185 # set metric to NaN
186 self.prom_metrics[flow_dict['metric_key']]. \
187 labels(vnf_name=vnf_name, vnf_interface=vnf_interface, flow_id=cookie). \
188 set(float('nan'))
stevenvanrossem1ef77022016-05-12 16:36:10 +0200189
190 delete_from_gateway(self.pushgateway, job='sonemu-SDNcontroller')
191
192 self.monitor_flow_lock.release()
193
194 logging.info('Stopped monitoring flow {3}: {2} on {0}:{1}'.format(vnf_name, vnf_interface, metric, cookie))
195 return 'Stopped monitoring flow {3}: {2} on {0}:{1}'.format(vnf_name, vnf_interface, metric, cookie)
196
stevenvanrossem9c8a4122016-07-16 03:23:13 +0200197 return 'Error stopping monitoring flow: {0} on {1}:{2}'.format(metric, vnf_name, vnf_interface)
198
stevenvanrossem461941c2016-05-10 11:41:29 +0200199
200 # first set some parameters, before measurement can start
stevenvanrossema24b4372016-04-14 09:55:20 +0200201 def setup_metric(self, vnf_name, vnf_interface=None, metric='tx_packets'):
202
203 network_metric = {}
204
stevenvanrossem9315da42016-04-11 12:10:06 +0200205 # check if port is specified (vnf:port)
stevenvanrossemc6ace2d2017-04-21 13:47:06 +0200206 if vnf_interface is None or vnf_interface == '':
stevenvanrossem9315da42016-04-11 12:10:06 +0200207 # take first interface by default
208 connected_sw = self.net.DCNetwork_graph.neighbors(vnf_name)[0]
209 link_dict = self.net.DCNetwork_graph[vnf_name][connected_sw]
210 vnf_interface = link_dict[0]['src_port_id']
stevenvanrossem9315da42016-04-11 12:10:06 +0200211
stevenvanrossema24b4372016-04-14 09:55:20 +0200212 network_metric['vnf_name'] = vnf_name
213 network_metric['vnf_interface'] = vnf_interface
stevenvanrossema24b4372016-04-14 09:55:20 +0200214
stevenvanrossem9315da42016-04-11 12:10:06 +0200215 for connected_sw in self.net.DCNetwork_graph.neighbors(vnf_name):
216 link_dict = self.net.DCNetwork_graph[vnf_name][connected_sw]
217 for link in link_dict:
stevenvanrossem9315da42016-04-11 12:10:06 +0200218 if link_dict[link]['src_port_id'] == vnf_interface:
219 # found the right link and connected switch
stevenvanrossem307aa1f2016-05-06 10:35:15 +0200220 network_metric['mon_port'] = link_dict[link]['dst_port_nr']
stevenvanrossem9315da42016-04-11 12:10:06 +0200221 break
222
stevenvanrossema24b4372016-04-14 09:55:20 +0200223 if 'mon_port' not in network_metric:
224 logging.exception("vnf interface {0}:{1} not found!".format(vnf_name,vnf_interface))
225 return "vnf interface {0}:{1} not found!".format(vnf_name,vnf_interface)
226
stevenvanrossem9315da42016-04-11 12:10:06 +0200227 try:
228 # default port direction to monitor
stevenvanrossemed711fd2016-04-11 16:59:29 +0200229 if metric is None:
stevenvanrossema24b4372016-04-14 09:55:20 +0200230 metric = 'tx_packets'
stevenvanrossem9315da42016-04-11 12:10:06 +0200231
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100232 vnf_switch = self.net.DCNetwork_graph.neighbors(str(vnf_name))
233
234 if len(vnf_switch) > 1:
235 logging.info("vnf: {0} has multiple ports".format(vnf_name))
236 return
237 elif len(vnf_switch) == 0:
238 logging.info("vnf: {0} is not connected".format(vnf_name))
239 return
240 else:
241 vnf_switch = vnf_switch[0]
242 next_node = self.net.getNodeByName(vnf_switch)
243
stevenvanrossemed711fd2016-04-11 16:59:29 +0200244 if not isinstance(next_node, OVSSwitch):
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100245 logging.info("vnf: {0} is not connected to switch".format(vnf_name))
246 return
247
stevenvanrossema24b4372016-04-14 09:55:20 +0200248 network_metric['previous_measurement'] = 0
249 network_metric['previous_monitor_time'] = 0
stevenvanrossemb098cb52016-04-15 13:28:23 +0200250
stevenvanrossem9315da42016-04-11 12:10:06 +0200251
stevenvanrossema24b4372016-04-14 09:55:20 +0200252 network_metric['switch_dpid'] = int(str(next_node.dpid), 16)
253 network_metric['metric_key'] = metric
stevenvanrossemb098cb52016-04-15 13:28:23 +0200254
stevenvanrossem300e1e52016-04-22 22:17:51 +0200255 self.monitor_lock.acquire()
stevenvanrossema24b4372016-04-14 09:55:20 +0200256 self.network_metrics.append(network_metric)
stevenvanrossem300e1e52016-04-22 22:17:51 +0200257 self.monitor_lock.release()
258
stevenvanrossema24b4372016-04-14 09:55:20 +0200259
260 logging.info('Started monitoring: {2} on {0}:{1}'.format(vnf_name, vnf_interface, metric))
261 return 'Started monitoring: {2} on {0}:{1}'.format(vnf_name, vnf_interface, metric)
stevenvanrossemc5a536a2016-02-16 14:52:39 +0100262
stevenvanrossemed711fd2016-04-11 16:59:29 +0200263 except Exception as ex:
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200264 logging.exception("setup_metric error.")
stevenvanrossemed711fd2016-04-11 16:59:29 +0200265 return ex.message
stevenvanrossem9315da42016-04-11 12:10:06 +0200266
stevenvanrossem461941c2016-05-10 11:41:29 +0200267 def stop_metric(self, vnf_name, vnf_interface=None, metric=None):
stevenvanrossem300e1e52016-04-22 22:17:51 +0200268
stevenvanrossem9c8a4122016-07-16 03:23:13 +0200269 # check if port is specified (vnf:port)
270 if vnf_interface is None and metric is not None:
271 # take first interface by default
272 connected_sw = self.net.DCNetwork_graph.neighbors(vnf_name)[0]
273 link_dict = self.net.DCNetwork_graph[vnf_name][connected_sw]
274 vnf_interface = link_dict[0]['src_port_id']
275
stevenvanrossem5d61e262017-05-07 22:40:44 +0200276 for metric_dict in deepcopy(self.network_metrics):
stevenvanrossemb098cb52016-04-15 13:28:23 +0200277 if metric_dict['vnf_name'] == vnf_name and metric_dict['vnf_interface'] == vnf_interface \
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200278 and metric_dict['metric_key'] == metric:
279
stevenvanrossem300e1e52016-04-22 22:17:51 +0200280 self.monitor_lock.acquire()
281
stevenvanrossemb098cb52016-04-15 13:28:23 +0200282 self.network_metrics.remove(metric_dict)
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200283
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200284 # set values to NaN, prometheus api currently does not support removal of metrics
stevenvanrossem300e1e52016-04-22 22:17:51 +0200285 #self.prom_metrics[metric_dict['metric_key']].labels(vnf_name, vnf_interface).set(float('nan'))
stevenvanrossembeba14d2017-01-28 15:40:30 +0100286 self.prom_metrics[metric_dict['metric_key']]. \
287 labels(vnf_name=vnf_name, vnf_interface=vnf_interface, flow_id=None). \
288 set(float('nan'))
stevenvanrossem300e1e52016-04-22 22:17:51 +0200289
290 # this removes the complete metric, all labels...
291 # 1 single monitor job for all metrics of the SDN controller
292 # we can only remove from the pushgateway grouping keys(labels) which we have defined for the add_to_pushgateway
293 # we can not specify labels from the metrics to be removed
294 # if we need to remove the metrics seperatelty, we need to give them a separate grouping key, and probably a diffferent registry also
295 delete_from_gateway(self.pushgateway, job='sonemu-SDNcontroller')
296
297 self.monitor_lock.release()
stevenvanrossembbdb5ee2016-04-15 15:18:44 +0200298
stevenvanrossemb098cb52016-04-15 13:28:23 +0200299 logging.info('Stopped monitoring: {2} on {0}:{1}'.format(vnf_name, vnf_interface, metric))
300 return 'Stopped monitoring: {2} on {0}:{1}'.format(vnf_name, vnf_interface, metric)
stevenvanrossem9315da42016-04-11 12:10:06 +0200301
stevenvanrossem461941c2016-05-10 11:41:29 +0200302 # delete everything from this vnf
303 elif metric_dict['vnf_name'] == vnf_name and vnf_interface is None and metric is None:
304 self.monitor_lock.acquire()
305 self.network_metrics.remove(metric_dict)
stevenvanrossem5d61e262017-05-07 22:40:44 +0200306 logging.info('remove metric from monitor: vnf_name:{0} vnf_interface:{1} mon_port:{2}'.format(metric_dict['vnf_name'], metric_dict['vnf_interface'], metric_dict['mon_port']))
stevenvanrossem461941c2016-05-10 11:41:29 +0200307
308 delete_from_gateway(self.pushgateway, job='sonemu-SDNcontroller')
309 self.monitor_lock.release()
stevenvanrossem5d61e262017-05-07 22:40:44 +0200310 continue
stevenvanrossem461941c2016-05-10 11:41:29 +0200311
stevenvanrossem5d61e262017-05-07 22:40:44 +0200312 if vnf_interface is None and metric is None:
313 logging.info('Stopped monitoring vnf: {0}'.format(vnf_name))
314 return 'Stopped monitoring: {0}'.format(vnf_name)
315 else:
316 return 'Error stopping monitoring metric: {0} on {1}:{2}'.format(metric, vnf_name, vnf_interface)
stevenvanrossemb098cb52016-04-15 13:28:23 +0200317
stevenvanrossem9c8a4122016-07-16 03:23:13 +0200318
stevenvanrossem9c8a4122016-07-16 03:23:13 +0200319# get all metrics defined in the list and export it to Prometheus
stevenvanrossem461941c2016-05-10 11:41:29 +0200320 def get_flow_metrics(self):
321 while self.start_monitoring:
322
323 self.monitor_flow_lock.acquire()
324
325 for flow_dict in self.flow_metrics:
326 data = {}
327
328 data['cookie'] = flow_dict['cookie']
stevenvanrossemb7715d32016-08-26 16:22:21 +0200329 data['cookie_mask'] = COOKIE_MASK
stevenvanrossem461941c2016-05-10 11:41:29 +0200330
331 if 'tx' in flow_dict['metric_key']:
332 data['match'] = {'in_port':flow_dict['mon_port']}
333 elif 'rx' in flow_dict['metric_key']:
334 data['out_port'] = flow_dict['mon_port']
335
336
337 # query Ryu
stevenvanrossem27b6d952016-05-10 16:37:57 +0200338 ret = self.net.ryu_REST('stats/flow', dpid=flow_dict['switch_dpid'], data=data)
stevenvanrossem51d4ae72016-08-10 13:22:53 +0200339 if isinstance(ret, dict):
340 flow_stat_dict = ret
341 elif isinstance(ret, basestring):
342 flow_stat_dict = ast.literal_eval(ret.rstrip())
343 else:
344 flow_stat_dict = None
345
stevenvanrosseme131bf52016-07-14 11:42:09 +0200346 logging.debug('received flow stat:{0} '.format(flow_stat_dict))
stevenvanrossem3fc13932016-08-09 23:39:16 +0200347
stevenvanrossem461941c2016-05-10 11:41:29 +0200348 self.set_flow_metric(flow_dict, flow_stat_dict)
349
stevenvanrossembeba14d2017-01-28 15:40:30 +0100350
351 try:
352 if len(self.flow_metrics) > 0:
353 pushadd_to_gateway(self.pushgateway, job='sonemu-SDNcontroller', registry=self.registry)
354 except Exception, e:
355 logging.warning("Pushgateway not reachable: {0} {1}".format(Exception, e))
356
stevenvanrossem461941c2016-05-10 11:41:29 +0200357 self.monitor_flow_lock.release()
358 time.sleep(1)
359
stevenvanrossema24b4372016-04-14 09:55:20 +0200360 def get_network_metrics(self):
stevenvanrossemb098cb52016-04-15 13:28:23 +0200361 while self.start_monitoring:
stevenvanrossem300e1e52016-04-22 22:17:51 +0200362
363 self.monitor_lock.acquire()
364
stevenvanrossema24b4372016-04-14 09:55:20 +0200365 # group metrics by dpid to optimize the rest api calls
366 dpid_list = [metric_dict['switch_dpid'] for metric_dict in self.network_metrics]
367 dpid_set = set(dpid_list)
368
369 for dpid in dpid_set:
370
371 # query Ryu
stevenvanrossem27b6d952016-05-10 16:37:57 +0200372 ret = self.net.ryu_REST('stats/port', dpid=dpid)
stevenvanrossemb7715d32016-08-26 16:22:21 +0200373 if isinstance(ret, dict):
374 port_stat_dict = ret
375 elif isinstance(ret, basestring):
376 port_stat_dict = ast.literal_eval(ret.rstrip())
377 else:
378 port_stat_dict = None
stevenvanrossema24b4372016-04-14 09:55:20 +0200379
380 metric_list = [metric_dict for metric_dict in self.network_metrics
381 if int(metric_dict['switch_dpid'])==int(dpid)]
stevenvanrosseme131bf52016-07-14 11:42:09 +0200382
stevenvanrossema24b4372016-04-14 09:55:20 +0200383 for metric_dict in metric_list:
384 self.set_network_metric(metric_dict, port_stat_dict)
385
stevenvanrossembeba14d2017-01-28 15:40:30 +0100386 try:
387 if len(self.network_metrics) > 0:
388 pushadd_to_gateway(self.pushgateway, job='sonemu-SDNcontroller', registry=self.registry)
389 except Exception, e:
390 logging.warning("Pushgateway not reachable: {0} {1}".format(Exception, e))
391
stevenvanrossem300e1e52016-04-22 22:17:51 +0200392 self.monitor_lock.release()
stevenvanrossema24b4372016-04-14 09:55:20 +0200393 time.sleep(1)
394
stevenvanrossemb098cb52016-04-15 13:28:23 +0200395 # add metric to the list to export to Prometheus, parse the Ryu port-stats reply
stevenvanrossema24b4372016-04-14 09:55:20 +0200396 def set_network_metric(self, metric_dict, port_stat_dict):
stevenvanrossemb098cb52016-04-15 13:28:23 +0200397 # vnf tx is the datacenter switch rx and vice-versa
398 metric_key = self.switch_tx_rx(metric_dict['metric_key'])
stevenvanrossema24b4372016-04-14 09:55:20 +0200399 switch_dpid = metric_dict['switch_dpid']
400 vnf_name = metric_dict['vnf_name']
401 vnf_interface = metric_dict['vnf_interface']
402 previous_measurement = metric_dict['previous_measurement']
403 previous_monitor_time = metric_dict['previous_monitor_time']
404 mon_port = metric_dict['mon_port']
stevenvanrossema24b4372016-04-14 09:55:20 +0200405 for port_stat in port_stat_dict[str(switch_dpid)]:
stevenvanrossemc6ace2d2017-04-21 13:47:06 +0200406 # ovs output also gives back 'LOCAL' port
407 if port_stat['port_no'] == 'LOCAL':
408 continue
stevenvanrossema24b4372016-04-14 09:55:20 +0200409 if int(port_stat['port_no']) == int(mon_port):
410 port_uptime = port_stat['duration_sec'] + port_stat['duration_nsec'] * 10 ** (-9)
411 this_measurement = int(port_stat[metric_key])
stevenvanrossema24b4372016-04-14 09:55:20 +0200412
413 # set prometheus metric
stevenvanrossem300e1e52016-04-22 22:17:51 +0200414 self.prom_metrics[metric_dict['metric_key']].\
stevenvanrossembeba14d2017-01-28 15:40:30 +0100415 labels(vnf_name=vnf_name, vnf_interface=vnf_interface, flow_id=None).\
stevenvanrossem300e1e52016-04-22 22:17:51 +0200416 set(this_measurement)
stevenvanrossem300e1e52016-04-22 22:17:51 +0200417
stevenvanrosseme131bf52016-07-14 11:42:09 +0200418 # also the rate is calculated here, but not used for now
419 # (rate can be easily queried from prometheus also)
stevenvanrossema24b4372016-04-14 09:55:20 +0200420 if previous_monitor_time <= 0 or previous_monitor_time >= port_uptime:
421 metric_dict['previous_measurement'] = int(port_stat[metric_key])
422 metric_dict['previous_monitor_time'] = port_uptime
423 # do first measurement
stevenvanrossemc721f282016-08-30 10:56:05 +0200424 #time.sleep(1)
425 #self.monitor_lock.release()
426 # rate cannot be calculated yet (need a first measurement)
427 metric_rate = None
stevenvanrossem61fd5282016-04-29 12:41:54 +0200428
stevenvanrossema24b4372016-04-14 09:55:20 +0200429 else:
430 time_delta = (port_uptime - metric_dict['previous_monitor_time'])
stevenvanrossem5d61e262017-05-07 22:40:44 +0200431 #metric_rate = (this_measurement - metric_dict['previous_measurement']) / float(time_delta)
stevenvanrossema24b4372016-04-14 09:55:20 +0200432
433 metric_dict['previous_measurement'] = this_measurement
434 metric_dict['previous_monitor_time'] = port_uptime
stevenvanrossemc721f282016-08-30 10:56:05 +0200435 return
stevenvanrossema24b4372016-04-14 09:55:20 +0200436
437 logging.exception('metric {0} not found on {1}:{2}'.format(metric_key, vnf_name, vnf_interface))
stevenvanrossemc721f282016-08-30 10:56:05 +0200438 logging.exception('monport:{0}, dpid:{1}'.format(mon_port, switch_dpid))
stevenvanrossem5d61e262017-05-07 22:40:44 +0200439 logging.exception('monitored network_metrics:{0}'.format(self.network_metrics))
stevenvanrossemc721f282016-08-30 10:56:05 +0200440 logging.exception('port dict:{0}'.format(port_stat_dict))
stevenvanrossema24b4372016-04-14 09:55:20 +0200441 return 'metric {0} not found on {1}:{2}'.format(metric_key, vnf_name, vnf_interface)
442
stevenvanrossem461941c2016-05-10 11:41:29 +0200443 def set_flow_metric(self, metric_dict, flow_stat_dict):
444 # vnf tx is the datacenter switch rx and vice-versa
stevenvanrossem461941c2016-05-10 11:41:29 +0200445 metric_key = metric_dict['metric_key']
446 switch_dpid = metric_dict['switch_dpid']
447 vnf_name = metric_dict['vnf_name']
448 vnf_interface = metric_dict['vnf_interface']
449 previous_measurement = metric_dict['previous_measurement']
450 previous_monitor_time = metric_dict['previous_monitor_time']
451 cookie = metric_dict['cookie']
stevenvanrossema24b4372016-04-14 09:55:20 +0200452
stevenvanrossem9c8a4122016-07-16 03:23:13 +0200453 counter = 0
454 for flow_stat in flow_stat_dict[str(switch_dpid)]:
455 if 'bytes' in metric_key:
456 counter += flow_stat['byte_count']
457 elif 'packet' in metric_key:
458 counter += flow_stat['packet_count']
459
stevenvanrossemc721f282016-08-30 10:56:05 +0200460 # flow_uptime disabled for now (can give error)
461 #flow_stat = flow_stat_dict[str(switch_dpid)][0]
462 #flow_uptime = flow_stat['duration_sec'] + flow_stat['duration_nsec'] * 10 ** (-9)
stevenvanrossem461941c2016-05-10 11:41:29 +0200463
464 self.prom_metrics[metric_dict['metric_key']]. \
stevenvanrossembeba14d2017-01-28 15:40:30 +0100465 labels(vnf_name=vnf_name, vnf_interface=vnf_interface, flow_id=cookie). \
stevenvanrossem461941c2016-05-10 11:41:29 +0200466 set(counter)
stevenvanrossemc6abf132016-04-14 11:15:58 +0200467
stevenvanrosseme50b0a72016-08-18 17:42:50 +0200468 def start_Prometheus(self, port=9090):
stevenvanrossemb098cb52016-04-15 13:28:23 +0200469 # prometheus.yml configuration file is located in the same directory as this file
stevenvanrossemc6abf132016-04-14 11:15:58 +0200470 cmd = ["docker",
471 "run",
472 "--rm",
473 "-p", "{0}:9090".format(port),
stevenvanrossemb098cb52016-04-15 13:28:23 +0200474 "-v", "{0}/prometheus.yml:/etc/prometheus/prometheus.yml".format(os.path.dirname(os.path.abspath(__file__))),
stevenvanrossem61fd5282016-04-29 12:41:54 +0200475 "-v", "{0}/profile.rules:/etc/prometheus/profile.rules".format(os.path.dirname(os.path.abspath(__file__))),
stevenvanrossemc6abf132016-04-14 11:15:58 +0200476 "--name", "prometheus",
477 "prom/prometheus"
478 ]
stevenvanrossemb098cb52016-04-15 13:28:23 +0200479 logging.info('Start Prometheus container {0}'.format(cmd))
480 return Popen(cmd)
stevenvanrossemc6abf132016-04-14 11:15:58 +0200481
stevenvanrossema5aeb372016-08-18 17:32:24 +0200482 def start_PushGateway(self, port=PUSHGATEWAY_PORT):
stevenvanrossemadfd06f2016-04-22 10:39:08 +0200483 cmd = ["docker",
484 "run",
485 "-d",
486 "-p", "{0}:9091".format(port),
487 "--name", "pushgateway",
stevenvanrossemc721f282016-08-30 10:56:05 +0200488 "--label", 'com.containernet=""',
stevenvanrossemadfd06f2016-04-22 10:39:08 +0200489 "prom/pushgateway"
490 ]
491
492 logging.info('Start Prometheus Push Gateway container {0}'.format(cmd))
493 return Popen(cmd)
494
stevenvanrosseme50b0a72016-08-18 17:42:50 +0200495 def start_cAdvisor(self, port=CADVISOR_PORT):
stevenvanrossemc6abf132016-04-14 11:15:58 +0200496 cmd = ["docker",
497 "run",
498 "--rm",
499 "--volume=/:/rootfs:ro",
500 "--volume=/var/run:/var/run:rw",
501 "--volume=/sys:/sys:ro",
502 "--volume=/var/lib/docker/:/var/lib/docker:ro",
503 "--publish={0}:8080".format(port),
504 "--name=cadvisor",
stevenvanrossemc721f282016-08-30 10:56:05 +0200505 "--label",'com.containernet=""',
stevenvanrossem6bed70f2017-04-24 03:14:44 +0200506 "--detach=true",
stevenvanrossem3df73e82017-04-22 19:37:29 +0200507 "google/cadvisor:latest",
stevenvanrossem6bed70f2017-04-24 03:14:44 +0200508 #"--storage_duration=1m0s",
509 #"--allow_dynamic_housekeeping=true",
stevenvanrossem0af81d42017-04-22 22:00:46 +0200510 #"--housekeeping_interval=1s",
stevenvanrossemc6abf132016-04-14 11:15:58 +0200511 ]
stevenvanrossemb098cb52016-04-15 13:28:23 +0200512 logging.info('Start cAdvisor container {0}'.format(cmd))
513 return Popen(cmd)
stevenvanrossemc6abf132016-04-14 11:15:58 +0200514
515 def stop(self):
stevenvanrossemb098cb52016-04-15 13:28:23 +0200516 # stop the monitoring thread
517 self.start_monitoring = False
518 self.monitor_thread.join()
stevenvanrossem461941c2016-05-10 11:41:29 +0200519 self.monitor_flow_thread.join()
stevenvanrossemb098cb52016-04-15 13:28:23 +0200520
stevenvanrossem9c8a4122016-07-16 03:23:13 +0200521 # these containers are used for monitoring but are started now outside of son-emu
stevenvanrossembeba14d2017-01-28 15:40:30 +0100522
stevenvanrossemadfd06f2016-04-22 10:39:08 +0200523 if self.pushgateway_process is not None:
524 logging.info('stopping pushgateway container')
stevenvanrossemadfd06f2016-04-22 10:39:08 +0200525 self._stop_container('pushgateway')
526
stevenvanrossemb098cb52016-04-15 13:28:23 +0200527 if self.cadvisor_process is not None:
528 logging.info('stopping cadvisor container')
stevenvanrossemb098cb52016-04-15 13:28:23 +0200529 self._stop_container('cadvisor')
530
531 def switch_tx_rx(self,metric=''):
532 # when monitoring vnfs, the tx of the datacenter switch is actually the rx of the vnf
533 # so we need to change the metric name to be consistent with the vnf rx or tx
534 if 'tx' in metric:
535 metric = metric.replace('tx','rx')
536 elif 'rx' in metric:
537 metric = metric.replace('rx','tx')
538
539 return metric
540
541 def _stop_container(self, name):
stevenvanrossemb098cb52016-04-15 13:28:23 +0200542
stevenvanrossemce032e12017-04-05 17:31:20 +0200543 #container = self.dockercli.containers.get(name)
544 #container.stop()
545 #container.remove(force=True)
546
547 # the only robust way to stop these containers is via Popen, it seems
548 time.sleep(1)
549 cmd = ['docker', 'rm', '-f', name]
550 Popen(cmd)
551
stevenvanrossembeba14d2017-01-28 15:40:30 +0100552
stevenvanrossemfb8c8392017-01-28 17:29:11 +0100553 def update_skewmon(self, vnf_name, resource_name, action):
554
555 ret = ''
556
557 config_file_path = '/tmp/skewmon.cfg'
558 configfile = open(config_file_path, 'a+')
559 try:
560 config = json.load(configfile)
561 except:
562 #not a valid json file or empty
563 config = {}
564
565 #initialize config file
566 if len(self.skewmon_metrics) == 0:
567 config = {}
568 json.dump(config, configfile)
569 configfile.close()
570
571 docker_name = 'mn.' + vnf_name
572 vnf_container = self.dockercli.containers.get(docker_name)
573 key = resource_name + '_' + vnf_container.short_id
574 vnf_id = vnf_container.id
575
576 if action == 'start':
577 # add a new vnf to monitor
578 config[key] = dict(VNF_NAME=vnf_name,
579 VNF_ID=vnf_id,
580 VNF_METRIC=resource_name)
581 ret = 'adding to skewness monitor: {0} {1} '.format(vnf_name, resource_name)
582 logging.info(ret)
583 elif action == 'stop':
584 # remove vnf to monitor
585 config.pop(key)
586 ret = 'removing from skewness monitor: {0} {1} '.format(vnf_name, resource_name)
587 logging.info(ret)
588
589 self.skewmon_metrics = config
590 configfile = open(config_file_path, 'w')
591 json.dump(config, configfile)
592 configfile.close()
593
594 try:
595 skewmon_container = self.dockercli.containers.get('skewmon')
596
597 # remove container if config is empty
598 if len(config) == 0:
599 ret += 'stopping skewness monitor'
600 logging.info('stopping skewness monitor')
601 skewmon_container.remove(force=True)
602
603 except docker.errors.NotFound:
604 # start container if not running
605 ret += 'starting skewness monitor'
606 logging.info('starting skewness monitor')
607 volumes = {'/sys/fs/cgroup':{'bind':'/sys/fs/cgroup', 'mode':'ro'},
608 '/tmp/skewmon.cfg':{'bind':'/config.txt', 'mode':'ro'}}
609 self.dockercli.containers.run('skewmon',
610 detach=True,
611 volumes=volumes,
612 labels=['com.containernet'],
613 name='skewmon'
614 )
stevenvanrossema7f601c2017-02-09 13:54:42 +0100615 # Wait a while for containers to be completely started
616 started = False
617 wait_time = 0
618 while not started:
619 list1 = self.dockercli.containers.list(filters={'status': 'running', 'name': 'prometheus'})
620 if len(list1) >= 1:
stevenvanrossem33d76892017-02-13 00:13:37 +0100621 time.sleep(1)
stevenvanrossema7f601c2017-02-09 13:54:42 +0100622 started = True
623 if wait_time > 5:
624 return 'skewmon not started'
625 time.sleep(1)
626 wait_time += 1
stevenvanrossemfb8c8392017-01-28 17:29:11 +0100627 return ret
628
stevenvanrossemc63c5492017-05-08 16:10:13 +0200629 def term(self, vnf_list=[]):
630 """
631 Start a terminal window for the specified VNFs
632 (start a terminal for all VNFs if vnf_list is empty)
633 :param vnf_list:
634 :return:
635 """
636
637
638 if vnf_list is None:
639 vnf_list = []
640 if not isinstance(vnf_list, list):
641 vnf_list = str(vnf_list).split(',')
642 vnf_list = map(str.strip, vnf_list)
643 logging.info('vnf_list: {}'.format(vnf_list))
644
645 return self.start_xterm(vnf_list)
646
647
648 # start an xterm for the specfified vnfs
649 def start_xterm(self, vnf_names):
650 # start xterm for all vnfs
651 for vnf_name in vnf_names:
652 terminal_cmd = "docker exec -it mn.{0} /bin/bash".format(vnf_name)
653
654 cmd = ['xterm', '-xrm', 'XTerm*selectToClipboard: true', '-xrm', 'XTerm.vt100.allowTitleOps: false',
655 '-T', vnf_name,
656 '-e', terminal_cmd]
657 Popen(cmd)
658
659 ret = 'xterms started for {0}'.format(vnf_names)
660 if len(vnf_names) == 0:
661 ret = 'vnf list is empty, no xterms started'
662 return ret
663
664
665
666
667
668
stevenvanrossemfb8c8392017-01-28 17:29:11 +0100669
670
671
stevenvanrossemb098cb52016-04-15 13:28:23 +0200672