Coverage for watcher/common/clients.py: 85%

153 statements  

« prev     ^ index     » next       coverage.py v7.8.2, created at 2025-06-17 12:22 +0000

1# Licensed under the Apache License, Version 2.0 (the "License"); you may 

2# not use this file except in compliance with the License. You may obtain 

3# a copy of the License at 

4# 

5# http://www.apache.org/licenses/LICENSE-2.0 

6# 

7# Unless required by applicable law or agreed to in writing, software 

8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 

9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 

10# License for the specific language governing permissions and limitations 

11# under the License. 

12 

13from oslo_config import cfg 

14 

15from cinderclient import client as ciclient 

16from glanceclient import client as glclient 

17from gnocchiclient import client as gnclient 

18from ironicclient import client as irclient 

19from keystoneauth1 import adapter as ka_adapter 

20from keystoneauth1 import loading as ka_loading 

21from keystoneclient import client as keyclient 

22from monascaclient import client as monclient 

23from neutronclient.neutron import client as netclient 

24from novaclient import api_versions as nova_api_versions 

25from novaclient import client as nvclient 

26 

27from watcher.common import exception 

28from watcher.common import utils 

29 

30try: 

31 from maas import client as maas_client 

32except ImportError: 

33 maas_client = None 

34 

35 

36CONF = cfg.CONF 

37 

38_CLIENTS_AUTH_GROUP = 'watcher_clients_auth' 

39 

40# NOTE(mriedem): This is the minimum required version of the nova API for 

41# watcher features to work. If new features are added which require new 

42# versions, they should perform version discovery and be backward compatible 

43# for at least one release before raising the minimum required version. 

44MIN_NOVA_API_VERSION = '2.56' 

45 

46 

47def check_min_nova_api_version(config_version): 

48 """Validates the minimum required nova API version. 

49 

50 :param config_version: The configured [nova_client]/api_version value 

51 :raises: ValueError if the configured version is less than the required 

52 minimum 

53 """ 

54 min_required = nova_api_versions.APIVersion(MIN_NOVA_API_VERSION) 

55 if nova_api_versions.APIVersion(config_version) < min_required: 

56 raise ValueError('Invalid nova_client.api_version %s. %s or ' 

57 'greater is required.' % (config_version, 

58 MIN_NOVA_API_VERSION)) 

59 

60 

61class OpenStackClients(object): 

62 """Convenience class to create and cache client instances.""" 

63 

64 def __init__(self): 

65 self.reset_clients() 

66 

67 def reset_clients(self): 

68 self._session = None 

69 self._keystone = None 

70 self._nova = None 

71 self._glance = None 

72 self._gnocchi = None 

73 self._cinder = None 

74 self._monasca = None 

75 self._neutron = None 

76 self._ironic = None 

77 self._maas = None 

78 self._placement = None 

79 

80 def _get_keystone_session(self): 

81 auth = ka_loading.load_auth_from_conf_options(CONF, 

82 _CLIENTS_AUTH_GROUP) 

83 sess = ka_loading.load_session_from_conf_options(CONF, 

84 _CLIENTS_AUTH_GROUP, 

85 auth=auth) 

86 return sess 

87 

88 @property 

89 def auth_url(self): 

90 return self.keystone().auth_url 

91 

92 @property 

93 def session(self): 

94 if not self._session: 

95 self._session = self._get_keystone_session() 

96 return self._session 

97 

98 def _get_client_option(self, client, option): 

99 return getattr(getattr(CONF, '%s_client' % client), option) 

100 

101 @exception.wrap_keystone_exception 

102 def keystone(self): 

103 if self._keystone: 

104 return self._keystone 

105 keystone_interface = self._get_client_option('keystone', 

106 'interface') 

107 keystone_region_name = self._get_client_option('keystone', 

108 'region_name') 

109 self._keystone = keyclient.Client( 

110 interface=keystone_interface, 

111 region_name=keystone_region_name, 

112 session=self.session) 

113 

114 return self._keystone 

115 

116 @exception.wrap_keystone_exception 

117 def nova(self): 

118 if self._nova: 

119 return self._nova 

120 

121 novaclient_version = self._get_client_option('nova', 'api_version') 

122 

123 check_min_nova_api_version(novaclient_version) 

124 

125 nova_endpoint_type = self._get_client_option('nova', 'endpoint_type') 

126 nova_region_name = self._get_client_option('nova', 'region_name') 

127 self._nova = nvclient.Client(novaclient_version, 

128 endpoint_type=nova_endpoint_type, 

129 region_name=nova_region_name, 

130 session=self.session) 

131 return self._nova 

132 

133 @exception.wrap_keystone_exception 

134 def glance(self): 

135 if self._glance: 

136 return self._glance 

137 

138 glanceclient_version = self._get_client_option('glance', 'api_version') 

139 glance_endpoint_type = self._get_client_option('glance', 

140 'endpoint_type') 

141 glance_region_name = self._get_client_option('glance', 'region_name') 

142 self._glance = glclient.Client(glanceclient_version, 

143 interface=glance_endpoint_type, 

144 region_name=glance_region_name, 

145 session=self.session) 

146 return self._glance 

147 

148 @exception.wrap_keystone_exception 

149 def gnocchi(self): 

150 if self._gnocchi: 

151 return self._gnocchi 

152 

153 gnocchiclient_version = self._get_client_option('gnocchi', 

154 'api_version') 

155 gnocchiclient_interface = self._get_client_option('gnocchi', 

156 'endpoint_type') 

157 gnocchiclient_region_name = self._get_client_option('gnocchi', 

158 'region_name') 

159 adapter_options = { 

160 "interface": gnocchiclient_interface, 

161 "region_name": gnocchiclient_region_name 

162 } 

163 

164 self._gnocchi = gnclient.Client(gnocchiclient_version, 

165 adapter_options=adapter_options, 

166 session=self.session) 

167 return self._gnocchi 

168 

169 @exception.wrap_keystone_exception 

170 def cinder(self): 

171 if self._cinder: 

172 return self._cinder 

173 

174 cinderclient_version = self._get_client_option('cinder', 'api_version') 

175 cinder_endpoint_type = self._get_client_option('cinder', 

176 'endpoint_type') 

177 cinder_region_name = self._get_client_option('cinder', 'region_name') 

178 self._cinder = ciclient.Client(cinderclient_version, 

179 endpoint_type=cinder_endpoint_type, 

180 region_name=cinder_region_name, 

181 session=self.session) 

182 return self._cinder 

183 

184 @exception.wrap_keystone_exception 

185 def monasca(self): 

186 if self._monasca: 

187 return self._monasca 

188 

189 monascaclient_version = self._get_client_option( 

190 'monasca', 'api_version') 

191 monascaclient_interface = self._get_client_option( 

192 'monasca', 'interface') 

193 monascaclient_region = self._get_client_option( 

194 'monasca', 'region_name') 

195 token = self.session.get_token() 

196 watcher_clients_auth_config = CONF.get(_CLIENTS_AUTH_GROUP) 

197 service_type = 'monitoring' 

198 monasca_kwargs = { 

199 'auth_url': watcher_clients_auth_config.auth_url, 

200 'cert_file': watcher_clients_auth_config.certfile, 

201 'insecure': watcher_clients_auth_config.insecure, 

202 'key_file': watcher_clients_auth_config.keyfile, 

203 'keystone_timeout': watcher_clients_auth_config.timeout, 

204 'os_cacert': watcher_clients_auth_config.cafile, 

205 'service_type': service_type, 

206 'token': token, 

207 'username': watcher_clients_auth_config.username, 

208 'password': watcher_clients_auth_config.password, 

209 } 

210 endpoint = self.session.get_endpoint(service_type=service_type, 

211 interface=monascaclient_interface, 

212 region_name=monascaclient_region) 

213 

214 self._monasca = monclient.Client( 

215 monascaclient_version, endpoint, **monasca_kwargs) 

216 

217 return self._monasca 

218 

219 @exception.wrap_keystone_exception 

220 def neutron(self): 

221 if self._neutron: 

222 return self._neutron 

223 

224 neutronclient_version = self._get_client_option('neutron', 

225 'api_version') 

226 neutron_endpoint_type = self._get_client_option('neutron', 

227 'endpoint_type') 

228 neutron_region_name = self._get_client_option('neutron', 'region_name') 

229 

230 self._neutron = netclient.Client(neutronclient_version, 

231 endpoint_type=neutron_endpoint_type, 

232 region_name=neutron_region_name, 

233 session=self.session) 

234 self._neutron.format = 'json' 

235 return self._neutron 

236 

237 @exception.wrap_keystone_exception 

238 def ironic(self): 

239 if self._ironic: 

240 return self._ironic 

241 

242 ironicclient_version = self._get_client_option('ironic', 'api_version') 

243 endpoint_type = self._get_client_option('ironic', 'endpoint_type') 

244 ironic_region_name = self._get_client_option('ironic', 'region_name') 

245 self._ironic = irclient.get_client(ironicclient_version, 

246 interface=endpoint_type, 

247 region_name=ironic_region_name, 

248 session=self.session) 

249 return self._ironic 

250 

251 def maas(self): 

252 if self._maas: 

253 return self._maas 

254 

255 if not maas_client: 

256 raise exception.UnsupportedError( 

257 "MAAS client unavailable. Please install python-libmaas.") 

258 

259 url = self._get_client_option('maas', 'url') 

260 api_key = self._get_client_option('maas', 'api_key') 

261 timeout = self._get_client_option('maas', 'timeout') 

262 self._maas = utils.async_compat_call( 

263 maas_client.connect, 

264 url, apikey=api_key, 

265 timeout=timeout) 

266 return self._maas 

267 

268 @exception.wrap_keystone_exception 

269 def placement(self): 

270 if self._placement: 270 ↛ 271line 270 didn't jump to line 271 because the condition on line 270 was never true

271 return self._placement 

272 

273 placement_version = self._get_client_option('placement', 

274 'api_version') 

275 placement_interface = self._get_client_option('placement', 

276 'interface') 

277 placement_region_name = self._get_client_option('placement', 

278 'region_name') 

279 # Set accept header on every request to ensure we notify placement 

280 # service of our response body media type preferences. 

281 headers = {'accept': 'application/json'} 

282 self._placement = ka_adapter.Adapter( 

283 session=self.session, 

284 service_type='placement', 

285 default_microversion=placement_version, 

286 interface=placement_interface, 

287 region_name=placement_region_name, 

288 additional_headers=headers) 

289 

290 return self._placement