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

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. 

14 

15import netaddr 

16from urllib import parse 

17 

18import requests 

19 

20from oslo_log import log 

21from oslo_serialization import jsonutils 

22 

23LOG = log.getLogger(__name__) 

24 

25 

26class OpenStackApiException(Exception): 

27 def __init__(self, message=None, response=None): 

28 self.response = response 

29 if not message: 

30 message = 'Unspecified error' 

31 

32 if response: 

33 _status = response.status_code 

34 _body = response.text 

35 

36 message = ('%(message)s\nStatus Code: %(_status)s\n' 

37 'Body: %(_body)s') % { 

38 "message": message, 

39 "_status": _status, 

40 "_body": _body 

41 } 

42 

43 super(OpenStackApiException, self).__init__(message) 

44 

45 

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) 

52 

53 

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) 

60 

61 

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) 

67 

68 

69class TestOpenStackClient(object): 

70 """Simple OpenStack API Client. 

71 

72 This is a really basic OpenStack API client that is under our control, 

73 so we can make changes / insert hooks for testing 

74 

75 """ 

76 

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' 

85 

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

90 

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

103 

104 _url = "%s://%s:%s%s" % (scheme, hostname, port, relative_url) 

105 

106 response = requests.request(method, _url, data=body, headers=_headers, 

107 verify=ssl_verify, stream=stream) 

108 return response 

109 

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 

113 

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) 

119 

120 http_status = response.status_code 

121 LOG.debug("%(endpoint)s => code %(http_status)s.", 

122 {"endpoint": self.endpoint, 

123 "http_status": http_status}) 

124 

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) 

127 

128 self.auth_result = response.headers 

129 return self.auth_result 

130 

131 def api_request(self, relative_uri, check_response_status=None, **kwargs): 

132 auth_result = self._authenticate() 

133 

134 base_uri = auth_result['x-server-management-url'] 

135 

136 full_uri = '%s/%s' % (base_uri, relative_uri) 

137 

138 headers = kwargs.setdefault('headers', {}) 

139 headers['X-Auth-Token'] = auth_result['x-auth-token'] 

140 

141 response = self.request(full_uri, **kwargs) 

142 

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

146 

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) 

157 

158 return response 

159 

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

167 

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) 

173 

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) 

178 

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) 

185 

186 kwargs.setdefault('check_response_status', [200, 202]) 

187 response = self.api_request(relative_uri, **kwargs) 

188 return self._decode_json(response) 

189 

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) 

196 

197 kwargs.setdefault('check_response_status', [200, 202, 204]) 

198 response = self.api_request(relative_uri, **kwargs) 

199 return self._decode_json(response) 

200 

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) 

205 

206 def get_shares(self, detail=True): 

207 rel_url = '/shares/detail' if detail else '/shares' 

208 return self.api_get(rel_url)['shares']