Coverage for watcher/decision_engine/datasources/manager.py: 84%
96 statements
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-17 12:22 +0000
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-17 12:22 +0000
1# -*- encoding: utf-8 -*-
2# Copyright 2017 NEC Corporation
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
16import os
17import yaml
19from collections import OrderedDict
20from oslo_config import cfg
21from oslo_log import log
23from watcher.common import exception
24from watcher.decision_engine.datasources import gnocchi as gnoc
25from watcher.decision_engine.datasources import grafana as graf
26from watcher.decision_engine.datasources import monasca as mon
27from watcher.decision_engine.datasources import prometheus as prom
29LOG = log.getLogger(__name__)
32class DataSourceManager(object):
34 metric_map = OrderedDict([
35 (gnoc.GnocchiHelper.NAME, gnoc.GnocchiHelper.METRIC_MAP),
36 (mon.MonascaHelper.NAME, mon.MonascaHelper.METRIC_MAP),
37 (graf.GrafanaHelper.NAME, graf.GrafanaHelper.METRIC_MAP),
38 (prom.PrometheusHelper.NAME, prom.PrometheusHelper.METRIC_MAP),
39 ])
40 """Dictionary with all possible datasources, dictionary order is
41 the default order for attempting to use datasources
42 """
44 def __init__(self, config=None, osc=None):
45 self.osc = osc
46 self.config = config
47 self._monasca = None
48 self._gnocchi = None
49 self._grafana = None
50 self._prometheus = None
52 # Dynamically update grafana metric map, only available at runtime
53 # The metric map can still be overridden by a yaml config file
54 self.metric_map[graf.GrafanaHelper.NAME] = self.grafana.METRIC_MAP
56 metric_map_path = cfg.CONF.watcher_decision_engine.metric_map_path
57 metrics_from_file = self.load_metric_map(metric_map_path)
58 for ds, mp in self.metric_map.items():
59 try:
60 self.metric_map[ds].update(metrics_from_file.get(ds, {}))
61 except KeyError:
62 msgargs = (ds, self.metric_map.keys())
63 LOG.warning('Invalid Datasource: %s. Allowed: %s ', *msgargs)
65 self.datasources = self.config.datasources
66 if self.datasources and mon.MonascaHelper.NAME in self.datasources:
67 LOG.warning('The monasca datasource is deprecated and will be '
68 'removed in a future release.')
70 @property
71 def monasca(self):
72 if self._monasca is None:
73 self._monasca = mon.MonascaHelper(osc=self.osc)
74 return self._monasca
76 @monasca.setter
77 def monasca(self, monasca):
78 self._monasca = monasca
80 @property
81 def gnocchi(self):
82 if self._gnocchi is None:
83 self._gnocchi = gnoc.GnocchiHelper(osc=self.osc)
84 return self._gnocchi
86 @gnocchi.setter
87 def gnocchi(self, gnocchi):
88 self._gnocchi = gnocchi
90 @property
91 def grafana(self):
92 if self._grafana is None:
93 self._grafana = graf.GrafanaHelper(osc=self.osc)
94 return self._grafana
96 @grafana.setter
97 def grafana(self, grafana):
98 self._grafana = grafana
100 @property
101 def prometheus(self):
102 if self._prometheus is None:
103 self._prometheus = prom.PrometheusHelper()
104 return self._prometheus
106 @prometheus.setter
107 def prometheus(self, prometheus):
108 self._prometheus = prometheus
110 def get_backend(self, metrics):
111 """Determine the datasource to use from the configuration
113 Iterates over the configured datasources in order to find the first
114 which can support all specified metrics. Upon a missing metric the next
115 datasource is attempted.
116 """
118 if not self.datasources or len(self.datasources) == 0:
119 raise exception.NoDatasourceAvailable
121 if not metrics or len(metrics) == 0:
122 LOG.critical("Can not retrieve datasource without specifying "
123 "list of required metrics.")
124 raise exception.InvalidParameter(parameter='metrics',
125 parameter_type='none empty list')
127 for datasource in self.datasources:
128 no_metric = False
129 for metric in metrics:
130 if (metric not in self.metric_map[datasource] or
131 self.metric_map[datasource].get(metric) is None):
132 no_metric = True
133 LOG.warning(
134 "Datasource: %s could not be used due to metric: %s",
135 datasource, metric)
136 break
137 if not no_metric:
138 # Try to use a specific datasource but attempt additional
139 # datasources upon exceptions (if config has more datasources)
140 try:
141 ds = getattr(self, datasource)
142 ds.METRIC_MAP.update(self.metric_map[ds.NAME])
143 return ds
144 except Exception:
145 pass # nosec: B110
146 raise exception.MetricNotAvailable(metric=metric)
148 def load_metric_map(self, file_path):
149 """Load metrics from the metric_map_path"""
150 if file_path and os.path.exists(file_path): 150 ↛ 151line 150 didn't jump to line 151 because the condition on line 150 was never true
151 with open(file_path, 'r') as f:
152 try:
153 ret = yaml.safe_load(f.read())
154 # return {} if the file is empty
155 return ret if ret else {}
156 except yaml.YAMLError as e:
157 LOG.warning('Could not load %s: %s', file_path, e)
158 return {}
159 else:
160 return {}