]>
Commit | Line | Data |
---|---|---|
11fdf7f2 | 1 | # -*- coding: utf-8 -*- |
11fdf7f2 | 2 | import json |
1e59de90 TL |
3 | import os |
4 | import tempfile | |
f67539c2 TL |
5 | from datetime import datetime |
6 | ||
11fdf7f2 TL |
7 | import requests |
8 | ||
1e59de90 | 9 | from .. import mgr |
f67539c2 | 10 | from ..exceptions import DashboardException |
11fdf7f2 | 11 | from ..security import Scope |
39ae355f | 12 | from ..services import ceph_service |
1e59de90 TL |
13 | from ..services.settings import SettingsService |
14 | from ..settings import Options, Settings | |
15 | from . import APIDoc, APIRouter, BaseController, Endpoint, RESTController, Router, UIRouter | |
11fdf7f2 TL |
16 | |
17 | ||
a4b75251 | 18 | @Router('/api/prometheus_receiver', secure=False) |
11fdf7f2 | 19 | class 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 |
32 | class 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 | 124 | class 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 |
152 | class 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) | |
164 | class 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 | } |