]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/dashboard/controllers/prometheus.py
bump version to 18.2.2-pve1
[ceph.git] / ceph / src / pybind / mgr / dashboard / controllers / prometheus.py
CommitLineData
11fdf7f2 1# -*- coding: utf-8 -*-
11fdf7f2 2import json
1e59de90
TL
3import os
4import tempfile
f67539c2
TL
5from datetime import datetime
6
11fdf7f2
TL
7import requests
8
1e59de90 9from .. import mgr
f67539c2 10from ..exceptions import DashboardException
11fdf7f2 11from ..security import Scope
39ae355f 12from ..services import ceph_service
1e59de90
TL
13from ..services.settings import SettingsService
14from ..settings import Options, Settings
15from . import APIDoc, APIRouter, BaseController, Endpoint, RESTController, Router, UIRouter
11fdf7f2
TL
16
17
a4b75251 18@Router('/api/prometheus_receiver', secure=False)
11fdf7f2 19class PrometheusReceiver(BaseController):
9f95a23c
TL
20 """
21 The receiver is needed in order to receive alert notifications (reports)
22 """
11fdf7f2
TL
23 notifications = []
24
b3b6e05e 25 @Endpoint('POST', path='/', version=None)
11fdf7f2
TL
26 def fetch_alert(self, **notification):
27 notification['notified'] = datetime.now().isoformat()
28 notification['id'] = str(len(self.notifications))
29 self.notifications.append(notification)
30
31
494da23a
TL
32class PrometheusRESTController(RESTController):
33 def prometheus_proxy(self, method, path, params=None, payload=None):
9f95a23c 34 # type (str, str, dict, dict)
1e59de90
TL
35 user, password, cert_file = self.get_access_info('prometheus')
36 verify = cert_file.name if cert_file else Settings.PROMETHEUS_API_SSL_VERIFY
37 response = self._proxy(self._get_api_url(Settings.PROMETHEUS_API_HOST),
38 method, path, 'Prometheus', params, payload,
39 user=user, password=password, verify=verify)
40 if cert_file:
41 cert_file.close()
42 os.unlink(cert_file.name)
43 return response
494da23a
TL
44
45 def alert_proxy(self, method, path, params=None, payload=None):
9f95a23c 46 # type (str, str, dict, dict)
1e59de90
TL
47 user, password, cert_file = self.get_access_info('alertmanager')
48 verify = cert_file.name if cert_file else Settings.ALERTMANAGER_API_SSL_VERIFY
49 response = self._proxy(self._get_api_url(Settings.ALERTMANAGER_API_HOST),
50 method, path, 'Alertmanager', params, payload,
51 user=user, password=password, verify=verify)
52 if cert_file:
53 cert_file.close()
54 os.unlink(cert_file.name)
55 return response
56
57 def get_access_info(self, module_name):
58 # type (str, str, str)
59 if module_name not in ['prometheus', 'alertmanager']:
60 raise DashboardException(f'Invalid module name {module_name}', component='prometheus')
61 user = None
62 password = None
63 cert_file = None
aee94f69
TL
64
65 orch_backend = mgr.get_module_option_ex('orchestrator', 'orchestrator')
66 if orch_backend == 'cephadm':
67 secure_monitoring_stack = mgr.get_module_option_ex('cephadm',
68 'secure_monitoring_stack',
69 False)
70 if secure_monitoring_stack:
71 cmd = {'prefix': f'orch {module_name} get-credentials'}
72 ret, out, _ = mgr.mon_command(cmd)
73 if ret == 0 and out is not None:
74 access_info = json.loads(out)
75 user = access_info['user']
76 password = access_info['password']
77 certificate = access_info['certificate']
78 cert_file = tempfile.NamedTemporaryFile(delete=False)
79 cert_file.write(certificate.encode('utf-8'))
80 cert_file.flush()
81
1e59de90 82 return user, password, cert_file
11fdf7f2 83
494da23a
TL
84 def _get_api_url(self, host):
85 return host.rstrip('/') + '/api/v1'
11fdf7f2 86
39ae355f
TL
87 def balancer_status(self):
88 return ceph_service.CephService.send_command('mon', 'balancer status')
89
1e59de90
TL
90 def _proxy(self, base_url, method, path, api_name, params=None, payload=None, verify=True,
91 user=None, password=None):
cd265ab1 92 # type (str, str, str, str, dict, dict, bool)
494da23a 93 try:
1e59de90
TL
94 from requests.auth import HTTPBasicAuth
95 auth = HTTPBasicAuth(user, password) if user and password else None
cd265ab1 96 response = requests.request(method, base_url + path, params=params,
1e59de90
TL
97 json=payload, verify=verify,
98 auth=auth)
494da23a 99 except Exception:
9f95a23c
TL
100 raise DashboardException(
101 "Could not reach {}'s API on {}".format(api_name, base_url),
102 http_status_code=404,
103 component='prometheus')
2a845540
TL
104 try:
105 content = json.loads(response.content, strict=False)
106 except json.JSONDecodeError as e:
107 raise DashboardException(
108 "Error parsing Prometheus Alertmanager response: {}".format(e.msg),
109 component='prometheus')
39ae355f
TL
110 balancer_status = self.balancer_status()
111 if content['status'] == 'success': # pylint: disable=R1702
1e59de90 112 alerts_info = []
494da23a 113 if 'data' in content:
39ae355f 114 if balancer_status['active'] and balancer_status['no_optimization_needed'] and path == '/alerts': # noqa E501 #pylint: disable=line-too-long
1e59de90
TL
115 alerts_info = [alert for alert in content['data'] if alert['labels']['alertname'] != 'CephPGImbalance'] # noqa E501 #pylint: disable=line-too-long
116 return alerts_info
494da23a
TL
117 return content['data']
118 return content
119 raise DashboardException(content, http_status_code=400, component='prometheus')
11fdf7f2 120
494da23a 121
a4b75251
TL
122@APIRouter('/prometheus', Scope.PROMETHEUS)
123@APIDoc("Prometheus Management API", "Prometheus")
494da23a 124class Prometheus(PrometheusRESTController):
11fdf7f2 125 def list(self, **params):
494da23a
TL
126 return self.alert_proxy('GET', '/alerts', params)
127
128 @RESTController.Collection(method='GET')
129 def rules(self, **params):
9f95a23c 130 return self.prometheus_proxy('GET', '/rules', params)
494da23a 131
1e59de90
TL
132 @RESTController.Collection(method='GET', path='/data')
133 def get_prometeus_data(self, **params):
134 params['query'] = params.pop('params')
135 return self.prometheus_proxy('GET', '/query_range', params)
136
494da23a
TL
137 @RESTController.Collection(method='GET', path='/silences')
138 def get_silences(self, **params):
139 return self.alert_proxy('GET', '/silences', params)
140
141 @RESTController.Collection(method='POST', path='/silence', status=201)
142 def create_silence(self, **params):
143 return self.alert_proxy('POST', '/silences', payload=params)
144
145 @RESTController.Collection(method='DELETE', path='/silence/{s_id}', status=204)
146 def delete_silence(self, s_id):
147 return self.alert_proxy('DELETE', '/silence/' + s_id) if s_id else None
11fdf7f2
TL
148
149
a4b75251
TL
150@APIRouter('/prometheus/notifications', Scope.PROMETHEUS)
151@APIDoc("Prometheus Notifications Management API", "PrometheusNotifications")
11fdf7f2
TL
152class PrometheusNotifications(RESTController):
153
154 def list(self, **params):
155 if 'from' in params:
156 f = params['from']
157 if f == 'last':
158 return PrometheusReceiver.notifications[-1:]
159 return PrometheusReceiver.notifications[int(f) + 1:]
160 return PrometheusReceiver.notifications
1e59de90
TL
161
162
163@UIRouter('/prometheus', Scope.PROMETHEUS)
164class PrometheusSettings(RESTController):
165 def get(self, name):
166 with SettingsService.attribute_handler(name) as settings_name:
167 setting = getattr(Options, settings_name)
168 return {
169 'name': settings_name,
170 'default': setting.default_value,
171 'type': setting.types_as_str(),
172 'value': getattr(Settings, settings_name)
173 }