diff --git a/alignak_backend/app.py b/alignak_backend/app.py index 3ee2d039..914d505f 100755 --- a/alignak_backend/app.py +++ b/alignak_backend/app.py @@ -1729,7 +1729,7 @@ def cron_timeseries(): @app.route("/cron_grafana") -def cron_grafana(engine='jsonify'): +def cron_grafana(engine='jsonify'): # pylint: disable=too-many-locals """ Cron used to add / update grafana dashboards @@ -1743,6 +1743,8 @@ def cron_grafana(engine='jsonify'): services_db = current_app.data.driver.db['service'] grafana_db = current_app.data.driver.db['grafana'] realm_db = current_app.data.driver.db['realm'] + graphite_db = current_app.data.driver.db['graphite'] + statsd_db = current_app.data.driver.db['statsd'] for grafana in grafana_db.find(): graf = Grafana(grafana) @@ -1751,6 +1753,17 @@ def cron_grafana(engine='jsonify'): "create_dashboard": [] } if graf.connection: + # get the graphites of the grafana + graphite_prefix = '' + statsd_prefix = '' + graphite = graphite_db.find_one({'grafana': grafana['_id']}) + if graphite and graphite['prefix'] != '': + graphite_prefix = graphite['prefix'] + if graphite and graphite['statsd']: + statsd = statsd_db.find_one({'_id': graphite['statsd']}) + if statsd and statsd['prefix'] != '': + statsd_prefix = statsd['prefix'] + # get the realms of the grafana realm = realm_db.find_one({'_id': grafana['_realm']}) if grafana['_sub_realm']: @@ -1762,10 +1775,12 @@ def cron_grafana(engine='jsonify'): items = hosts_db.find({'ls_grafana': False, '_realm': realm['_id'], 'ls_perf_data': {"$ne": ""}, '_is_template': False}) for item in items: - created = graf.create_dashboard(item['_id']) + created = graf.create_dashboard(item['_id'], + graphite_prefix, statsd_prefix) if created: resp[grafana['name']]['create_dashboard'].append(item['name']) - # manage the cases hosts have new services or hosts not have ls_perf_data + + # manage the case where hosts have new services or hosts do not have ls_perf_data hosts_dashboards = {} if grafana['_sub_realm']: children = realm['_all_children'] @@ -1779,7 +1794,8 @@ def cron_grafana(engine='jsonify'): if item['host'] not in hosts_dashboards: host = hosts_db.find_one({'_id': item['host']}) if not host['_is_template']: - created = graf.create_dashboard(item['host']) + created = graf.create_dashboard(item['host'], + graphite_prefix, statsd_prefix) if created: resp[grafana['name']]['create_dashboard'].append(host['name']) hosts_dashboards[item['host']] = True diff --git a/alignak_backend/grafana.py b/alignak_backend/grafana.py index 8d3bf637..f1b82b4e 100644 --- a/alignak_backend/grafana.py +++ b/alignak_backend/grafana.py @@ -59,7 +59,8 @@ def __init__(self, data): self.timeseries[child_realm] = influxdb self.get_datasource() - def create_dashboard(self, host_id): # pylint: disable=too-many-locals + def create_dashboard(self, host_id, graphite_prefix='', statsd_prefix=''): + # pylint: disable=too-many-locals """ Create / update a dashboard in Grafana @@ -96,25 +97,64 @@ def create_dashboard(self, host_id): # pylint: disable=too-many-locals for measurement in perfdata.metrics: fields = perfdata.metrics[measurement].__dict__ fields['name'] = fields['name'].replace(" ", "_") - mytarget = Timeseries.get_realms_prefix(host['_realm']) + '.' + hostname + # Add statsd, graphite and realm prefixes + mytarget = '.'.join([statsd_prefix, graphite_prefix]) + mytarget += '.' + Timeseries.get_realms_prefix(host['_realm']) + '.' + hostname mytarget += '.' + fields['name'] - elements = {'measurement': fields['name'], 'refid': refids[num], 'mytarget': mytarget} + while mytarget.startswith('.'): + mytarget = mytarget[1:] + + try: + elements = {'measurement': fields['name'], 'refid': refids[num], + 'mytarget': mytarget} + except IndexError as e: + print("-----\nToo much metrics in the performance data for '%s'!\n-----" + % (host['name'])) + elements = {'measurement': fields['name'], 'refid': '', 'mytarget': mytarget} + targets.append(self.generate_target(elements, {"host": hostname}, ObjectId(host['_realm']))) - num += 1 if fields['warning'] is not None: - mytarget = Timeseries.get_realms_prefix(host['_realm']) + '.' + hostname + num += 1 + # Add statsd, graphite and realm prefixes + mytarget = '.'.join([statsd_prefix, graphite_prefix]) + mytarget += '.' + Timeseries.get_realms_prefix(host['_realm']) + '.' + hostname mytarget += '.' + fields['name'] + '_warning' - elements = {'measurement': fields['name'] + '_warning', 'refid': refids[num], - 'mytarget': mytarget} + while mytarget.startswith('.'): + mytarget = mytarget[1:] + + try: + elements = {'measurement': fields['name'] + '_warning', 'refid': refids[num], + 'mytarget': mytarget} + except IndexError as e: + print("-----\nToo much metrics (warning) in the " + "performance data for '%s'!\n-----" + % (host['name'])) + elements = {'measurement': fields['name'] + '_warning', 'refid': '', + 'mytarget': mytarget} + targets.append(self.generate_target(elements, {"host": hostname}, ObjectId(host['_realm']))) - num += 1 + if fields['critical'] is not None: - mytarget = Timeseries.get_realms_prefix(host['_realm']) + '.' + hostname + num += 1 + # Add statsd, graphite and realm prefixes + mytarget = '.'.join([statsd_prefix, graphite_prefix]) + mytarget += '.' + Timeseries.get_realms_prefix(host['_realm']) + '.' + hostname mytarget += '.' + fields['name'] + '_critical' - elements = {'measurement': fields['name'] + '_critical', 'refid': refids[num], - 'mytarget': mytarget} + while mytarget.startswith('.'): + mytarget = mytarget[1:] + + try: + elements = {'measurement': fields['name'] + '_critical', 'refid': refids[num], + 'mytarget': mytarget} + except IndexError as e: + print("-----\nToo much metrics (critical) in the " + "performance data for '%s'!\n-----" + % (host['name'])) + elements = {'measurement': fields['name'] + '_critical', 'refid': '', + 'mytarget': mytarget} + targets.append(self.generate_target(elements, {"host": hostname}, ObjectId(host['_realm']))) num += 1 @@ -141,30 +181,70 @@ def create_dashboard(self, host_id): # pylint: disable=too-many-locals for measurement in perfdata.metrics: fields = perfdata.metrics[measurement].__dict__ fields['name'] = fields['name'].replace(" ", "_") - mytarget = Timeseries.get_realms_prefix(host['_realm']) + '.' + hostname + # Add statsd, graphite and realm prefixes + mytarget = '.'.join([statsd_prefix, graphite_prefix]) + mytarget += '.' + Timeseries.get_realms_prefix(host['_realm']) + '.' + hostname mytarget += '.' + service['name'] + '.' + fields['name'] - elements = {'measurement': fields['name'], 'refid': refids[num], - 'mytarget': mytarget} + while mytarget.startswith('.'): + mytarget = mytarget[1:] + + try: + elements = {'measurement': fields['name'], 'refid': refids[num], + 'mytarget': mytarget} + except IndexError as e: + print("-----\nToo much metrics in the performance data for '%s/%s'!\n-----" + % (host['name'], service['name'])) + elements = {'measurement': fields['name'], 'refid': '', + 'mytarget': mytarget} + targets.append(self.generate_target(elements, {"host": hostname, "service": service['name']}, ObjectId(service['_realm']))) - num += 1 if fields['warning'] is not None: - mytarget = Timeseries.get_realms_prefix(host['_realm']) + '.' + hostname + num += 1 + # Add statsd, graphite and realm prefixes + mytarget = '.'.join([statsd_prefix, graphite_prefix]) + mytarget += '.' + Timeseries.get_realms_prefix(host['_realm']) + mytarget += '.' + hostname mytarget += '.' + service['name'] + '.' + fields['name'] + "_warning" - elements = {'measurement': fields['name'] + "_warning", - 'refid': refids[num], 'mytarget': mytarget} + while mytarget.startswith('.'): + mytarget = mytarget[1:] + + try: + elements = {'measurement': fields['name'] + "_warning", + 'refid': refids[num], 'mytarget': mytarget} + except IndexError as e: + print("-----\nToo much metrics (warning) in the " + "performance data for '%s/%s'!\n-----" + % (host['name'], service['name'])) + elements = {'measurement': fields['name'] + "_warning", + 'refid': '', 'mytarget': mytarget} + targets.append(self.generate_target(elements, {"host": hostname, "service": service['name']}, ObjectId(service['_realm']))) - num += 1 if fields['critical'] is not None: - mytarget = Timeseries.get_realms_prefix(host['_realm']) + '.' + hostname + num += 1 + # Add statsd, graphite and realm prefixes + mytarget = '.'.join([statsd_prefix, graphite_prefix]) + mytarget += '.' + Timeseries.get_realms_prefix(host['_realm']) + mytarget += '.' + hostname mytarget += '.' + service['name'] + '.' + fields['name'] + "_critical" - elements = {'measurement': fields['name'] + "_critical", - 'refid': refids[num], 'mytarget': mytarget} + while mytarget.startswith('.'): + mytarget = mytarget[1:] + + try: + elements = {'measurement': fields['name'] + "_critical", + 'refid': refids[num], 'mytarget': mytarget} + except IndexError as e: + print("-----\nToo much metrics (critical) in the " + "performance data for '%s/%s'!\n-----" + % (host['name'], service['name'])) + elements = {'measurement': fields['name'] + "_critical", + 'refid': '', 'mytarget': mytarget} + targets.append(self.generate_target(elements, {"host": hostname, "service": service['name']}, diff --git a/alignak_backend/models/statsd.py b/alignak_backend/models/statsd.py index 05d05c3c..5897e0fe 100755 --- a/alignak_backend/models/statsd.py +++ b/alignak_backend/models/statsd.py @@ -43,6 +43,10 @@ def get_schema(): 'empty': False, 'default': 8125 }, + 'prefix': { + 'type': 'string', + 'default': '', + }, '_realm': { 'type': 'objectid', 'data_relation': { diff --git a/alignak_backend/timeseries.py b/alignak_backend/timeseries.py index 1729365f..04f0ca3e 100644 --- a/alignak_backend/timeseries.py +++ b/alignak_backend/timeseries.py @@ -263,6 +263,11 @@ def send_to_statsd(data, statsd_id, prefix): """ Send data to statsd + Do not take care of the StatsD prefix because it is managed internally by the + StatsD daemon + + Provide the Graphite prefix to the StatsD client + :param data: list of perfdata to send to statsd :type data: list :param statsd_id: id of statsd diff --git a/test/test_grafana.py b/test/test_grafana.py index 4f174f33..9ba9b936 100644 --- a/test/test_grafana.py +++ b/test/test_grafana.py @@ -446,6 +446,155 @@ def test_create_dashboard_panels_graphite(self): assert srv002['ls_grafana'] assert srv002['ls_grafana_panelid'] == 2 + def test_create_dashboard_panels_graphite_statsd(self): + # pylint: disable=too-many-locals + """ + Create dashboard into grafana with datasource graphite and statsd. + Use prefixes for Graphite and StatsD + + :return: None + """ + headers = {'Content-Type': 'application/json'} + # Create grafana in realm All + subrealm + data = { + 'name': 'grafana All', + 'address': '192.168.0.101', + 'apikey': 'xxxxxxxxxxxx1', + '_realm': self.realm_all, + '_sub_realm': True + } + response = requests.post(self.endpoint + '/grafana', json=data, headers=headers, + auth=self.auth) + resp = response.json() + self.assertEqual('OK', resp['_status'], resp) + grafana_all = resp['_id'] + + # Create statsd in realm All + subrealm + data = { + 'name': 'statsd All', + 'address': '192.168.0.101', + 'port': 8125, + 'prefix': 'alignak-statsd', + '_realm': self.realm_all, + '_sub_realm': True + } + response = requests.post(self.endpoint + '/statsd', json=data, headers=headers, + auth=self.auth) + resp = response.json() + self.assertEqual('OK', resp['_status'], resp) + statsd_all = resp['_id'] + + # Create a graphite in All B linked to grafana and statsd + data = { + 'name': 'graphite B', + 'carbon_address': '192.168.0.101', + 'graphite_address': '192.168.0.101', + 'prefix': 'my_B', + 'grafana': grafana_all, + 'statsd': statsd_all, + '_realm': self.realmAll_B + } + response = requests.post(self.endpoint + '/graphite', json=data, headers=headers, + auth=self.auth) + resp = response.json() + self.assertEqual('OK', resp['_status'], resp) + graphite_B = resp['_id'] + + # Create a graphite in All A + subrealm liked to grafana and statsd + data = { + 'name': 'graphite A sub', + 'carbon_address': '192.168.0.102', + 'graphite_address': '192.168.0.102', + 'prefix': 'my_A_sub', + 'grafana': grafana_all, + 'statsd': statsd_all, + '_realm': self.realmAll_A, + '_sub_realm': True + } + response = requests.post(self.endpoint + '/graphite', json=data, headers=headers, + auth=self.auth) + resp = response.json() + self.assertEqual('OK', resp['_status'], resp) + graphite_A_sub = resp['_id'] + + # test grafana class and code to create dashboard in grafana + from alignak_backend.app import app, current_app + with app.app_context(): + grafana_db = current_app.data.driver.db['grafana'] + grafanas = grafana_db.find() + for grafana in grafanas: + with requests_mock.mock() as mockreq: + ret = [{"id": 1, "orgId": 1, "name": graphite_B, + "type": "grafana-simple-json-datasource", + "typeLogoUrl": "public/plugins/grafana-simple-json-datasource/src/img/" + "simpleJson_logo.svg", + "access": "proxy", "url": "http://127.0.0.1/glpi090/apirest.php", + "password": "", "user": "", "database": "", "basicAuth": True, + "basicAuthUser": "", "basicAuthPassword": "", "withCredentials": False, + "isDefault": True}] + mockreq.get('http://192.168.0.101:3000/api/datasources', json=ret) + mockreq.post('http://192.168.0.101:3000/api/datasources', json='true') + graf = Grafana(grafana) + assert len(graf.timeseries) == 3 + assert sorted([ObjectId(self.realmAll_B), ObjectId(self.realmAll_A), + ObjectId(self.realmAll_A1)]) == sorted(graf.timeseries.keys()) + assert graf.timeseries[ObjectId(self.realmAll_A)]['_id'] == ObjectId( + graphite_A_sub) + assert graf.timeseries[ObjectId(self.realmAll_A1)]['_id'] == ObjectId( + graphite_A_sub) + assert graf.timeseries[ObjectId(self.realmAll_B)]['_id'] == ObjectId( + graphite_B) + history = mockreq.request_history + methods = {'POST': 0, 'GET': 0} + for h in history: + methods[h.method] += 1 + assert {'POST': 1, 'GET': 1} == methods + + # create a dashboard for a host + with app.test_request_context(): + with requests_mock.mock() as mockreq: + ret = [{"id": 1, "orgId": 1, "name": graphite_B, + "type": "grafana-simple-json-datasource", + "typeLogoUrl": "public/plugins/grafana-simple-json-datasource/src/" + "img/simpleJson_logo.svg", + "access": "proxy", "url": "http://127.0.0.1/glpi090/apirest.php", + "password": "", "user": "", "database": "", "basicAuth": True, + "basicAuthUser": "", "basicAuthPassword": "", + "withCredentials": False, "isDefault": True}, + {"id": 2, "orgId": 1, "name": graphite_A_sub, + "type": "grafana-simple-json-datasource", + "typeLogoUrl": "public/plugins/grafana-simple-json-datasource/src/" + "img/simpleJson_logo.svg", + "access": "proxy", "url": "http://127.0.0.1/glpi090/apirest.php", + "password": "", "user": "", "database": "", "basicAuth": True, + "basicAuthUser": "", "basicAuthPassword": "", + "withCredentials": False, "isDefault": False}] + mockreq.get('http://192.168.0.101:3000/api/datasources', json=ret) + mockreq.post('http://192.168.0.101:3000/api/datasources/db', json='true') + mockreq.post('http://192.168.0.101:3000/api/dashboards/db', json='true') + graf = Grafana(grafana) + assert not graf.create_dashboard(ObjectId(self.host_srv001), + 'alignak-graphite', 'alignak-statsd') + assert graf.create_dashboard(ObjectId(self.host_srv002), + 'alignak-graphite', 'alignak-statsd') + history = mockreq.request_history + methods = {'POST': 0, 'GET': 0} + for h in history: + methods[h.method] += 1 + if h.method == 'POST': + dash = h.json() + assert len(dash['dashboard']['rows']) == 2 + assert {'POST': 1, 'GET': 1} == methods + # check host and the service are tagged grafana and have the id + host_db = current_app.data.driver.db['host'] + host002 = host_db.find_one({'_id': ObjectId(self.host_srv002)}) + assert host002['ls_grafana'] + assert host002['ls_grafana_panelid'] == 1 + service_db = current_app.data.driver.db['service'] + srv002 = service_db.find_one({'_id': ObjectId(self.host_srv002_srv)}) + assert srv002['ls_grafana'] + assert srv002['ls_grafana_panelid'] == 2 + def test_grafana_connection_error(self): """ This test the connection error of grafana