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
« 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.
13from oslo_config import cfg
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
27from watcher.common import exception
28from watcher.common import utils
30try:
31 from maas import client as maas_client
32except ImportError:
33 maas_client = None
36CONF = cfg.CONF
38_CLIENTS_AUTH_GROUP = 'watcher_clients_auth'
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'
47def check_min_nova_api_version(config_version):
48 """Validates the minimum required nova API version.
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))
61class OpenStackClients(object):
62 """Convenience class to create and cache client instances."""
64 def __init__(self):
65 self.reset_clients()
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
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
88 @property
89 def auth_url(self):
90 return self.keystone().auth_url
92 @property
93 def session(self):
94 if not self._session:
95 self._session = self._get_keystone_session()
96 return self._session
98 def _get_client_option(self, client, option):
99 return getattr(getattr(CONF, '%s_client' % client), option)
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)
114 return self._keystone
116 @exception.wrap_keystone_exception
117 def nova(self):
118 if self._nova:
119 return self._nova
121 novaclient_version = self._get_client_option('nova', 'api_version')
123 check_min_nova_api_version(novaclient_version)
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
133 @exception.wrap_keystone_exception
134 def glance(self):
135 if self._glance:
136 return self._glance
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
148 @exception.wrap_keystone_exception
149 def gnocchi(self):
150 if self._gnocchi:
151 return self._gnocchi
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 }
164 self._gnocchi = gnclient.Client(gnocchiclient_version,
165 adapter_options=adapter_options,
166 session=self.session)
167 return self._gnocchi
169 @exception.wrap_keystone_exception
170 def cinder(self):
171 if self._cinder:
172 return self._cinder
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
184 @exception.wrap_keystone_exception
185 def monasca(self):
186 if self._monasca:
187 return self._monasca
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)
214 self._monasca = monclient.Client(
215 monascaclient_version, endpoint, **monasca_kwargs)
217 return self._monasca
219 @exception.wrap_keystone_exception
220 def neutron(self):
221 if self._neutron:
222 return self._neutron
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')
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
237 @exception.wrap_keystone_exception
238 def ironic(self):
239 if self._ironic:
240 return self._ironic
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
251 def maas(self):
252 if self._maas:
253 return self._maas
255 if not maas_client:
256 raise exception.UnsupportedError(
257 "MAAS client unavailable. Please install python-libmaas.")
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
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
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)
290 return self._placement