Coverage for manila/tests/integrated/api/client.py: 52%
123 statements
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
1# Copyright (c) 2011 Justin Santa Barbara
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
15import netaddr
16from urllib import parse
18import requests
20from oslo_log import log
21from oslo_serialization import jsonutils
23LOG = log.getLogger(__name__)
26class OpenStackApiException(Exception):
27 def __init__(self, message=None, response=None):
28 self.response = response
29 if not message:
30 message = 'Unspecified error'
32 if response:
33 _status = response.status_code
34 _body = response.text
36 message = ('%(message)s\nStatus Code: %(_status)s\n'
37 'Body: %(_body)s') % {
38 "message": message,
39 "_status": _status,
40 "_body": _body
41 }
43 super(OpenStackApiException, self).__init__(message)
46class OpenStackApiAuthenticationException(OpenStackApiException):
47 def __init__(self, response=None, message=None):
48 if not message:
49 message = "Authentication error"
50 super(OpenStackApiAuthenticationException, self).__init__(message,
51 response)
54class OpenStackApiAuthorizationException(OpenStackApiException):
55 def __init__(self, response=None, message=None):
56 if not message:
57 message = "Authorization error"
58 super(OpenStackApiAuthorizationException, self).__init__(message,
59 response)
62class OpenStackApiNotFoundException(OpenStackApiException):
63 def __init__(self, response=None, message=None):
64 if not message:
65 message = "Item not found"
66 super(OpenStackApiNotFoundException, self).__init__(message, response)
69class TestOpenStackClient(object):
70 """Simple OpenStack API Client.
72 This is a really basic OpenStack API client that is under our control,
73 so we can make changes / insert hooks for testing
75 """
77 def __init__(self, auth_user, auth_key, endpoint):
78 super(TestOpenStackClient, self).__init__()
79 self.auth_result = None
80 self.auth_user = auth_user
81 self.auth_key = auth_key
82 self.endpoint = endpoint
83 # default project_id
84 self.project_id = 'openstack'
86 def request(self, url, method='GET', body=None, headers=None,
87 ssl_verify=True, stream=False):
88 _headers = {'Content-Type': 'application/json'}
89 _headers.update(headers or {})
91 parsed_url = parse.urlparse(url)
92 port = parsed_url.port or ''
93 hostname = parsed_url.hostname
94 if netaddr.valid_ipv6(hostname): 94 ↛ 95line 94 didn't jump to line 95 because the condition on line 94 was never true
95 hostname = "[%s]" % hostname
96 scheme = parsed_url.scheme
97 relative_url = parsed_url.path
98 if parsed_url.query: 98 ↛ 99line 98 didn't jump to line 99 because the condition on line 98 was never true
99 relative_url = relative_url + "?" + parsed_url.query
100 LOG.debug("Doing %(method)s on %(relative_url)s, body: %(body)s",
101 {"method": method, "relative_url": relative_url,
102 "body": body or {}})
104 _url = "%s://%s:%s%s" % (scheme, hostname, port, relative_url)
106 response = requests.request(method, _url, data=body, headers=_headers,
107 verify=ssl_verify, stream=stream)
108 return response
110 def _authenticate(self):
111 if self.auth_result: 111 ↛ 112line 111 didn't jump to line 112 because the condition on line 111 was never true
112 return self.auth_result
114 headers = {'X-Auth-User': self.auth_user,
115 'X-Auth-Key': self.auth_key,
116 'X-Auth-Project-Id': self.project_id}
117 response = self.request(self.endpoint,
118 headers=headers)
120 http_status = response.status_code
121 LOG.debug("%(endpoint)s => code %(http_status)s.",
122 {"endpoint": self.endpoint,
123 "http_status": http_status})
125 if http_status == 401: 125 ↛ 126line 125 didn't jump to line 126 because the condition on line 125 was never true
126 raise OpenStackApiAuthenticationException(response=response)
128 self.auth_result = response.headers
129 return self.auth_result
131 def api_request(self, relative_uri, check_response_status=None, **kwargs):
132 auth_result = self._authenticate()
134 base_uri = auth_result['x-server-management-url']
136 full_uri = '%s/%s' % (base_uri, relative_uri)
138 headers = kwargs.setdefault('headers', {})
139 headers['X-Auth-Token'] = auth_result['x-auth-token']
141 response = self.request(full_uri, **kwargs)
143 http_status = response.status_code
144 LOG.debug("%(relative_uri)s => code %(http_status)s.",
145 {"relative_uri": relative_uri, "http_status": http_status})
147 if check_response_status:
148 if http_status not in check_response_status: 148 ↛ 149line 148 didn't jump to line 149 because the condition on line 148 was never true
149 if http_status == 404:
150 raise OpenStackApiNotFoundException(response=response)
151 elif http_status == 401:
152 raise OpenStackApiAuthorizationException(response=response)
153 else:
154 raise OpenStackApiException(
155 message="Unexpected status code",
156 response=response)
158 return response
160 def _decode_json(self, response):
161 body = response.text
162 LOG.debug("Decoding JSON: %s.", (body))
163 if body: 163 ↛ 166line 163 didn't jump to line 166 because the condition on line 163 was always true
164 return jsonutils.loads(body)
165 else:
166 return ""
168 def api_options(self, relative_uri, **kwargs):
169 kwargs['method'] = 'OPTIONS'
170 kwargs.setdefault('check_response_status', [200])
171 response = self.api_request(relative_uri, **kwargs)
172 return self._decode_json(response)
174 def api_get(self, relative_uri, **kwargs):
175 kwargs.setdefault('check_response_status', [200])
176 response = self.api_request(relative_uri, **kwargs)
177 return self._decode_json(response)
179 def api_post(self, relative_uri, body, **kwargs):
180 kwargs['method'] = 'POST'
181 if body:
182 headers = kwargs.setdefault('headers', {})
183 headers['Content-Type'] = 'application/json'
184 kwargs['body'] = jsonutils.dumps(body)
186 kwargs.setdefault('check_response_status', [200, 202])
187 response = self.api_request(relative_uri, **kwargs)
188 return self._decode_json(response)
190 def api_put(self, relative_uri, body, **kwargs):
191 kwargs['method'] = 'PUT'
192 if body:
193 headers = kwargs.setdefault('headers', {})
194 headers['Content-Type'] = 'application/json'
195 kwargs['body'] = jsonutils.dumps(body)
197 kwargs.setdefault('check_response_status', [200, 202, 204])
198 response = self.api_request(relative_uri, **kwargs)
199 return self._decode_json(response)
201 def api_delete(self, relative_uri, **kwargs):
202 kwargs['method'] = 'DELETE'
203 kwargs.setdefault('check_response_status', [200, 202, 204])
204 return self.api_request(relative_uri, **kwargs)
206 def get_shares(self, detail=True):
207 rel_url = '/shares/detail' if detail else '/shares'
208 return self.api_get(rel_url)['shares']