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

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. 

15 

16import os 

17import yaml 

18 

19from collections import OrderedDict 

20from oslo_config import cfg 

21from oslo_log import log 

22 

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 

28 

29LOG = log.getLogger(__name__) 

30 

31 

32class DataSourceManager(object): 

33 

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 """ 

43 

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 

51 

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 

55 

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) 

64 

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.') 

69 

70 @property 

71 def monasca(self): 

72 if self._monasca is None: 

73 self._monasca = mon.MonascaHelper(osc=self.osc) 

74 return self._monasca 

75 

76 @monasca.setter 

77 def monasca(self, monasca): 

78 self._monasca = monasca 

79 

80 @property 

81 def gnocchi(self): 

82 if self._gnocchi is None: 

83 self._gnocchi = gnoc.GnocchiHelper(osc=self.osc) 

84 return self._gnocchi 

85 

86 @gnocchi.setter 

87 def gnocchi(self, gnocchi): 

88 self._gnocchi = gnocchi 

89 

90 @property 

91 def grafana(self): 

92 if self._grafana is None: 

93 self._grafana = graf.GrafanaHelper(osc=self.osc) 

94 return self._grafana 

95 

96 @grafana.setter 

97 def grafana(self, grafana): 

98 self._grafana = grafana 

99 

100 @property 

101 def prometheus(self): 

102 if self._prometheus is None: 

103 self._prometheus = prom.PrometheusHelper() 

104 return self._prometheus 

105 

106 @prometheus.setter 

107 def prometheus(self, prometheus): 

108 self._prometheus = prometheus 

109 

110 def get_backend(self, metrics): 

111 """Determine the datasource to use from the configuration 

112 

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 """ 

117 

118 if not self.datasources or len(self.datasources) == 0: 

119 raise exception.NoDatasourceAvailable 

120 

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') 

126 

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) 

147 

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 {}