blob: 6fdbf1859cceb8d1d4cee638d7ee7ab1cec3f114 [file] [log] [blame]
Oleh Hryhorov7594fa82019-02-26 13:45:12 +00001import functools
Vasyl Saienko4eda4f22018-04-26 19:30:39 +03002import logging
Oleh Hryhorov7594fa82019-02-26 13:45:12 +00003import time
Oleksiy Petrenko77a7e9c2019-02-06 13:03:53 +02004
5try:
6 import os_client_config
7except ImportError:
8 os_client_config = None
9from salt import exceptions
Vasyl Saienko4eda4f22018-04-26 19:30:39 +030010
Oleh Hryhorov7594fa82019-02-26 13:45:12 +000011from keystoneclient import exceptions as ks_exceptions
12
Vasyl Saienko4eda4f22018-04-26 19:30:39 +030013log = logging.getLogger(__name__)
14
15
16class KeystoneException(Exception):
17
18 _msg = "Keystone module exception occured."
19
20 def __init__(self, message=None, **kwargs):
21 super(KeystoneException, self).__init__(message or self._msg)
22
23
24class NoKeystoneEndpoint(KeystoneException):
25 _msg = "Keystone endpoint not found in keystone catalog."
26
27
28class NoAuthPluginConfigured(KeystoneException):
29 _msg = ("You are using keystoneauth auth plugin that does not support "
30 "fetching endpoint list from token (noauth or admin_token).")
31
32
33class NoCredentials(KeystoneException):
34 _msg = "Please provide cloud name present in clouds.yaml."
35
36
37class ResourceNotFound(KeystoneException):
38 _msg = "Uniq resource: {resource} with name: {name} not found."
39
40 def __init__(self, resource, name, **kwargs):
41 super(KeystoneException, self).__init__(
42 self._msg.format(resource=resource, name=name))
43
44
45class MultipleResourcesFound(KeystoneException):
46 _msg = "Multiple resource: {resource} with name: {name} found."
47
48 def __init__(self, resource, name, **kwargs):
49 super(KeystoneException, self).__init__(
50 self._msg.format(resource=resource, name=name))
51
52
53def _get_raw_client(cloud_name):
Oleksiy Petrenko77a7e9c2019-02-06 13:03:53 +020054 if not os_client_config:
55 raise exceptions.SaltInvocationError(
56 "Cannot load os-client-config. Please check your environment "
57 "configuration.")
Vasyl Saienko4eda4f22018-04-26 19:30:39 +030058 service_type = 'identity'
59 config = os_client_config.OpenStackConfig()
60 cloud = config.get_one_cloud(cloud_name)
61 adapter = cloud.get_session_client(service_type)
62 adapter.version = '3'
63 try:
64 access_info = adapter.session.auth.get_access(adapter.session)
65 endpoints = access_info.service_catalog.get_endpoints()
66 except (AttributeError, ValueError):
67 e = NoAuthPluginConfigured()
68 log.exception('%s' % e)
69 raise e
70 if service_type not in endpoints:
71 if not service_type:
72 e = NoKeystoneEndpoint()
73 log.error('%s' % e)
74 raise e
75 return adapter
76
77
78def send(method, microversion_header=None):
79 def wrap(func):
Oleh Hryhorov7594fa82019-02-26 13:45:12 +000080 @functools.wraps(func)
Vasyl Saienko4eda4f22018-04-26 19:30:39 +030081 def wrapped_f(*args, **kwargs):
82 headers = kwargs.pop('headers', {})
83 if kwargs.get('microversion'):
84 headers.setdefault(microversion_header,
85 kwargs.get('microversion'))
86 cloud_name = kwargs.pop('cloud_name')
Oleh Hryhorov7594fa82019-02-26 13:45:12 +000087 connect_retries = 30
88 connect_retry_delay = 1
Vasyl Saienko4eda4f22018-04-26 19:30:39 +030089 if not cloud_name:
90 e = NoCredentials()
91 log.error('%s' % e)
92 raise e
Oleh Hryhorov7594fa82019-02-26 13:45:12 +000093 adapter = None
94 for i in range(connect_retries):
95 try:
96 adapter = _get_raw_client(cloud_name)
97 except (ks_exceptions.DiscoveryFailure) as e:
98 msg = ("Got exception when determining a suitable "
99 "URL for Keystone plugin. Sleeping for %ss. Attepmpts "
100 "%s of %s")
101 log.error(msg % (connect_retry_delay, i, connect_retries))
102 time.sleep(connect_retry_delay)
103 continue
104 break
105 if not adapter:
106 raise ks_exceptions.DiscoveryFailure("Could not connect to Keystone API to determine a suitable URL for the plugin")
Vasyl Saienko4eda4f22018-04-26 19:30:39 +0300107 # Remove salt internal kwargs
108 kwarg_keys = list(kwargs.keys())
109 for k in kwarg_keys:
110 if k.startswith('__'):
111 kwargs.pop(k)
Oleh Hryhorov7594fa82019-02-26 13:45:12 +0000112 url, json = func(*args, **kwargs)
113 response = None
Oleksandr Shyshkodaaa1ae2019-04-18 13:16:31 +0000114 last_exception = None
Oleh Hryhorov7594fa82019-02-26 13:45:12 +0000115 for i in range(connect_retries):
116 try:
117 response = getattr(adapter, method)(
118 url, connect_retries=connect_retries,
119 json=json)
120 except Exception as e:
Oleksandr Shyshkodaaa1ae2019-04-18 13:16:31 +0000121 last_exception = e
Oleh Hryhorov7594fa82019-02-26 13:45:12 +0000122 if not hasattr(e, 'http_status') or (e.http_status >= 500
123 or e.http_status == 0):
124 msg = ("Got retriable exception when contacting "
125 "Keystone API. Sleeping for %ss. Attepmpts "
126 "%s of %s")
127 log.error(msg % (connect_retry_delay, i, connect_retries))
128 time.sleep(connect_retry_delay)
129 continue
Oleksandr Shyshkodaaa1ae2019-04-18 13:16:31 +0000130 else:
131 break
132 else:
133 last_exception = None
134 break
135 if last_exception:
136 raise KeystoneException(last_exception.message)
Oleh Hryhorov7594fa82019-02-26 13:45:12 +0000137 if not response or not response.content:
Vasyl Saienko4eda4f22018-04-26 19:30:39 +0300138 return {}
139 try:
140 resp = response.json()
Oleh Hryhorov7594fa82019-02-26 13:45:12 +0000141 except ValueError:
Vasyl Saienko4eda4f22018-04-26 19:30:39 +0300142 resp = response.content
143 return resp
144 return wrapped_f
145 return wrap