summaryrefslogtreecommitdiffstats
path: root/src/pybind/mgr/dashboard/controllers/prometheus.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/pybind/mgr/dashboard/controllers/prometheus.py')
-rw-r--r--src/pybind/mgr/dashboard/controllers/prometheus.py173
1 files changed, 173 insertions, 0 deletions
diff --git a/src/pybind/mgr/dashboard/controllers/prometheus.py b/src/pybind/mgr/dashboard/controllers/prometheus.py
new file mode 100644
index 000000000..b639d8826
--- /dev/null
+++ b/src/pybind/mgr/dashboard/controllers/prometheus.py
@@ -0,0 +1,173 @@
+# -*- coding: utf-8 -*-
+import json
+import os
+import tempfile
+from datetime import datetime
+
+import requests
+
+from .. import mgr
+from ..exceptions import DashboardException
+from ..security import Scope
+from ..services import ceph_service
+from ..services.settings import SettingsService
+from ..settings import Options, Settings
+from . import APIDoc, APIRouter, BaseController, Endpoint, RESTController, Router, UIRouter
+
+
+@Router('/api/prometheus_receiver', secure=False)
+class PrometheusReceiver(BaseController):
+ """
+ The receiver is needed in order to receive alert notifications (reports)
+ """
+ notifications = []
+
+ @Endpoint('POST', path='/', version=None)
+ def fetch_alert(self, **notification):
+ notification['notified'] = datetime.now().isoformat()
+ notification['id'] = str(len(self.notifications))
+ self.notifications.append(notification)
+
+
+class PrometheusRESTController(RESTController):
+ def prometheus_proxy(self, method, path, params=None, payload=None):
+ # type (str, str, dict, dict)
+ user, password, cert_file = self.get_access_info('prometheus')
+ verify = cert_file.name if cert_file else Settings.PROMETHEUS_API_SSL_VERIFY
+ response = self._proxy(self._get_api_url(Settings.PROMETHEUS_API_HOST),
+ method, path, 'Prometheus', params, payload,
+ user=user, password=password, verify=verify)
+ if cert_file:
+ cert_file.close()
+ os.unlink(cert_file.name)
+ return response
+
+ def alert_proxy(self, method, path, params=None, payload=None):
+ # type (str, str, dict, dict)
+ user, password, cert_file = self.get_access_info('alertmanager')
+ verify = cert_file.name if cert_file else Settings.ALERTMANAGER_API_SSL_VERIFY
+ response = self._proxy(self._get_api_url(Settings.ALERTMANAGER_API_HOST),
+ method, path, 'Alertmanager', params, payload,
+ user=user, password=password, verify=verify)
+ if cert_file:
+ cert_file.close()
+ os.unlink(cert_file.name)
+ return response
+
+ def get_access_info(self, module_name):
+ # type (str, str, str)
+ if module_name not in ['prometheus', 'alertmanager']:
+ raise DashboardException(f'Invalid module name {module_name}', component='prometheus')
+ user = None
+ password = None
+ cert_file = None
+
+ orch_backend = mgr.get_module_option_ex('orchestrator', 'orchestrator')
+ if orch_backend == 'cephadm':
+ secure_monitoring_stack = mgr.get_module_option_ex('cephadm',
+ 'secure_monitoring_stack',
+ False)
+ if secure_monitoring_stack:
+ cmd = {'prefix': f'orch {module_name} get-credentials'}
+ ret, out, _ = mgr.mon_command(cmd)
+ if ret == 0 and out is not None:
+ access_info = json.loads(out)
+ user = access_info['user']
+ password = access_info['password']
+ certificate = access_info['certificate']
+ cert_file = tempfile.NamedTemporaryFile(delete=False)
+ cert_file.write(certificate.encode('utf-8'))
+ cert_file.flush()
+
+ return user, password, cert_file
+
+ def _get_api_url(self, host):
+ return host.rstrip('/') + '/api/v1'
+
+ def balancer_status(self):
+ return ceph_service.CephService.send_command('mon', 'balancer status')
+
+ def _proxy(self, base_url, method, path, api_name, params=None, payload=None, verify=True,
+ user=None, password=None):
+ # type (str, str, str, str, dict, dict, bool)
+ try:
+ from requests.auth import HTTPBasicAuth
+ auth = HTTPBasicAuth(user, password) if user and password else None
+ response = requests.request(method, base_url + path, params=params,
+ json=payload, verify=verify,
+ auth=auth)
+ except Exception:
+ raise DashboardException(
+ "Could not reach {}'s API on {}".format(api_name, base_url),
+ http_status_code=404,
+ component='prometheus')
+ try:
+ content = json.loads(response.content, strict=False)
+ except json.JSONDecodeError as e:
+ raise DashboardException(
+ "Error parsing Prometheus Alertmanager response: {}".format(e.msg),
+ component='prometheus')
+ balancer_status = self.balancer_status()
+ if content['status'] == 'success': # pylint: disable=R1702
+ alerts_info = []
+ if 'data' in content:
+ if balancer_status['active'] and balancer_status['no_optimization_needed'] and path == '/alerts': # noqa E501 #pylint: disable=line-too-long
+ alerts_info = [alert for alert in content['data'] if alert['labels']['alertname'] != 'CephPGImbalance'] # noqa E501 #pylint: disable=line-too-long
+ return alerts_info
+ return content['data']
+ return content
+ raise DashboardException(content, http_status_code=400, component='prometheus')
+
+
+@APIRouter('/prometheus', Scope.PROMETHEUS)
+@APIDoc("Prometheus Management API", "Prometheus")
+class Prometheus(PrometheusRESTController):
+ def list(self, **params):
+ return self.alert_proxy('GET', '/alerts', params)
+
+ @RESTController.Collection(method='GET')
+ def rules(self, **params):
+ return self.prometheus_proxy('GET', '/rules', params)
+
+ @RESTController.Collection(method='GET', path='/data')
+ def get_prometeus_data(self, **params):
+ params['query'] = params.pop('params')
+ return self.prometheus_proxy('GET', '/query_range', params)
+
+ @RESTController.Collection(method='GET', path='/silences')
+ def get_silences(self, **params):
+ return self.alert_proxy('GET', '/silences', params)
+
+ @RESTController.Collection(method='POST', path='/silence', status=201)
+ def create_silence(self, **params):
+ return self.alert_proxy('POST', '/silences', payload=params)
+
+ @RESTController.Collection(method='DELETE', path='/silence/{s_id}', status=204)
+ def delete_silence(self, s_id):
+ return self.alert_proxy('DELETE', '/silence/' + s_id) if s_id else None
+
+
+@APIRouter('/prometheus/notifications', Scope.PROMETHEUS)
+@APIDoc("Prometheus Notifications Management API", "PrometheusNotifications")
+class PrometheusNotifications(RESTController):
+
+ def list(self, **params):
+ if 'from' in params:
+ f = params['from']
+ if f == 'last':
+ return PrometheusReceiver.notifications[-1:]
+ return PrometheusReceiver.notifications[int(f) + 1:]
+ return PrometheusReceiver.notifications
+
+
+@UIRouter('/prometheus', Scope.PROMETHEUS)
+class PrometheusSettings(RESTController):
+ def get(self, name):
+ with SettingsService.attribute_handler(name) as settings_name:
+ setting = getattr(Options, settings_name)
+ return {
+ 'name': settings_name,
+ 'default': setting.default_value,
+ 'type': setting.types_as_str(),
+ 'value': getattr(Settings, settings_name)
+ }