blob: 2231407e4bb9f729508be6853da960f213c5bac0 [file] [log] [blame]
ZhiQiang Fan39f97222013-09-20 04:49:44 +08001# Copyright 2012 OpenStack Foundation
dwalleck5d734432012-10-04 01:11:47 -05002# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
Daisuke Morita02f840b2014-03-19 20:51:01 +090016import httplib
Daisuke Morita2c87d372013-10-31 19:39:19 +090017import urllib
Daisuke Morita02f840b2014-03-19 20:51:01 +090018import urlparse
Daisuke Morita2c87d372013-10-31 19:39:19 +090019
Mate Lakat23a58a32013-08-23 02:06:22 +010020from tempest.common import http
Eiichi Aikawaca3d9bd2014-03-06 15:06:21 +090021from tempest.common import rest_client
Matthew Treinish684d8992014-01-30 16:27:40 +000022from tempest import config
harika-vakadi6ab397b2012-12-20 12:16:17 +053023from tempest import exceptions
dwalleck5d734432012-10-04 01:11:47 -050024
Matthew Treinish684d8992014-01-30 16:27:40 +000025CONF = config.CONF
26
dwalleck5d734432012-10-04 01:11:47 -050027
Eiichi Aikawaca3d9bd2014-03-06 15:06:21 +090028class ObjectClient(rest_client.RestClient):
Andrea Frittoli8bbdb162014-01-06 11:06:13 +000029 def __init__(self, auth_provider):
30 super(ObjectClient, self).__init__(auth_provider)
dwalleck5d734432012-10-04 01:11:47 -050031
Matthew Treinish684d8992014-01-30 16:27:40 +000032 self.service = CONF.object_storage.catalog_type
dwalleck5d734432012-10-04 01:11:47 -050033
Daisuke Moritacf6f6952014-03-19 21:25:50 +090034 def create_object(self, container, object_name, data,
35 params=None, metadata=None):
Sean Daguef237ccb2013-01-04 15:19:14 -050036 """Create storage object."""
dwalleck5d734432012-10-04 01:11:47 -050037
Valeriy Ponomaryov88686d82014-02-16 12:24:51 +020038 headers = self.get_headers()
Martina Kollarova03720a52013-06-18 15:08:46 +020039 if not data:
40 headers['content-length'] = '0'
Daisuke Moritacf6f6952014-03-19 21:25:50 +090041 if metadata:
42 for key in metadata:
43 headers[str(key)] = metadata[key]
dwalleck5d734432012-10-04 01:11:47 -050044 url = "%s/%s" % (str(container), str(object_name))
Daisuke Morita2c87d372013-10-31 19:39:19 +090045 if params:
46 url += '?%s' % urllib.urlencode(params)
47
Martina Kollarova03720a52013-06-18 15:08:46 +020048 resp, body = self.put(url, data, headers)
JordanPa84dde32014-11-14 15:47:42 +010049 self.expected_success(201, resp.status)
dwalleck5d734432012-10-04 01:11:47 -050050 return resp, body
51
52 def update_object(self, container, object_name, data):
Sean Daguef237ccb2013-01-04 15:19:14 -050053 """Upload data to replace current storage object."""
JordanPa84dde32014-11-14 15:47:42 +010054 resp, body = self.create_object(container, object_name, data)
55 self.expected_success(201, resp.status)
56 return resp, body
dwalleck5d734432012-10-04 01:11:47 -050057
Daisuke Morita2c87d372013-10-31 19:39:19 +090058 def delete_object(self, container, object_name, params=None):
Sean Daguef237ccb2013-01-04 15:19:14 -050059 """Delete storage object."""
dwalleck5d734432012-10-04 01:11:47 -050060 url = "%s/%s" % (str(container), str(object_name))
Daisuke Morita2c87d372013-10-31 19:39:19 +090061 if params:
62 url += '?%s' % urllib.urlencode(params)
vponomaryov67b58fe2014-02-06 19:05:41 +020063 resp, body = self.delete(url, headers={})
JordanPa84dde32014-11-14 15:47:42 +010064 self.expected_success([200, 204], resp.status)
dwalleck5d734432012-10-04 01:11:47 -050065 return resp, body
66
67 def update_object_metadata(self, container, object_name, metadata,
68 metadata_prefix='X-Object-Meta-'):
Sean Daguef237ccb2013-01-04 15:19:14 -050069 """Add, remove, or change X-Object-Meta metadata for storage object."""
dwalleck5d734432012-10-04 01:11:47 -050070
71 headers = {}
72 for key in metadata:
73 headers["%s%s" % (str(metadata_prefix), str(key))] = metadata[key]
74
75 url = "%s/%s" % (str(container), str(object_name))
76 resp, body = self.post(url, None, headers=headers)
JordanPa84dde32014-11-14 15:47:42 +010077 self.expected_success(202, resp.status)
dwalleck5d734432012-10-04 01:11:47 -050078 return resp, body
79
80 def list_object_metadata(self, container, object_name):
Sean Daguef237ccb2013-01-04 15:19:14 -050081 """List all storage object X-Object-Meta- metadata."""
dwalleck5d734432012-10-04 01:11:47 -050082
83 url = "%s/%s" % (str(container), str(object_name))
84 resp, body = self.head(url)
JordanPa84dde32014-11-14 15:47:42 +010085 self.expected_success(200, resp.status)
dwalleck5d734432012-10-04 01:11:47 -050086 return resp, body
Larisa Ustalov6c3c7802012-11-05 12:25:19 +020087
Daisuke Morita6d502682014-03-19 21:08:54 +090088 def get_object(self, container, object_name, metadata=None):
Larisa Ustalov6c3c7802012-11-05 12:25:19 +020089 """Retrieve object's data."""
90
Daisuke Morita6d502682014-03-19 21:08:54 +090091 headers = {}
92 if metadata:
93 for key in metadata:
94 headers[str(key)] = metadata[key]
95
Larisa Ustalov6c3c7802012-11-05 12:25:19 +020096 url = "{0}/{1}".format(container, object_name)
Daisuke Morita6d502682014-03-19 21:08:54 +090097 resp, body = self.get(url, headers=headers)
JordanPa84dde32014-11-14 15:47:42 +010098 self.expected_success([200, 206], resp.status)
Larisa Ustalov6c3c7802012-11-05 12:25:19 +020099 return resp, body
100
rajalakshmi-ganesan925e2392012-11-30 19:17:24 +0530101 def copy_object_in_same_container(self, container, src_object_name,
102 dest_object_name, metadata=None):
Sean Daguef237ccb2013-01-04 15:19:14 -0500103 """Copy storage object's data to the new object using PUT."""
Larisa Ustalov6c3c7802012-11-05 12:25:19 +0200104
105 url = "{0}/{1}".format(container, dest_object_name)
106 headers = {}
107 headers['X-Copy-From'] = "%s/%s" % (str(container),
108 str(src_object_name))
109 headers['content-length'] = '0'
110 if metadata:
111 for key in metadata:
112 headers[str(key)] = metadata[key]
113
114 resp, body = self.put(url, None, headers=headers)
JordanPa84dde32014-11-14 15:47:42 +0100115 self.expected_success(201, resp.status)
Larisa Ustalov6c3c7802012-11-05 12:25:19 +0200116 return resp, body
117
rajalakshmi-ganesan925e2392012-11-30 19:17:24 +0530118 def copy_object_across_containers(self, src_container, src_object_name,
119 dst_container, dst_object_name,
120 metadata=None):
Sean Daguef237ccb2013-01-04 15:19:14 -0500121 """Copy storage object's data to the new object using PUT."""
rajalakshmi-ganesan925e2392012-11-30 19:17:24 +0530122
123 url = "{0}/{1}".format(dst_container, dst_object_name)
124 headers = {}
125 headers['X-Copy-From'] = "%s/%s" % (str(src_container),
126 str(src_object_name))
127 headers['content-length'] = '0'
128 if metadata:
129 for key in metadata:
130 headers[str(key)] = metadata[key]
131
132 resp, body = self.put(url, None, headers=headers)
JordanPa84dde32014-11-14 15:47:42 +0100133 self.expected_success(201, resp.status)
rajalakshmi-ganesan925e2392012-11-30 19:17:24 +0530134 return resp, body
135
Larisa Ustalov6c3c7802012-11-05 12:25:19 +0200136 def copy_object_2d_way(self, container, src_object_name, dest_object_name,
137 metadata=None):
Sean Daguef237ccb2013-01-04 15:19:14 -0500138 """Copy storage object's data to the new object using COPY."""
Larisa Ustalov6c3c7802012-11-05 12:25:19 +0200139
140 url = "{0}/{1}".format(container, src_object_name)
141 headers = {}
142 headers['Destination'] = "%s/%s" % (str(container),
143 str(dest_object_name))
144 if metadata:
145 for key in metadata:
146 headers[str(key)] = metadata[key]
147
148 resp, body = self.copy(url, headers=headers)
JordanPa84dde32014-11-14 15:47:42 +0100149 self.expected_success(201, resp.status)
Larisa Ustalov6c3c7802012-11-05 12:25:19 +0200150 return resp, body
harika-vakadi1a9ad612012-12-14 19:12:08 +0530151
harika-vakadi7cfc5182013-01-16 13:59:25 +0530152 def create_object_segments(self, container, object_name, segment, data):
153 """Creates object segments."""
154 url = "{0}/{1}/{2}".format(container, object_name, segment)
Valeriy Ponomaryov88686d82014-02-16 12:24:51 +0200155 resp, body = self.put(url, data)
JordanPa84dde32014-11-14 15:47:42 +0100156 self.expected_success(201, resp.status)
ravikumar-venkatesane43b77a2013-01-09 07:27:13 +0000157 return resp, body
158
Daisuke Morita02f840b2014-03-19 20:51:01 +0900159 def put_object_with_chunk(self, container, name, contents, chunk_size):
160 """
161 Put an object with Transfer-Encoding header
162 """
163 if self.base_url is None:
164 self._set_auth()
165
166 headers = {'Transfer-Encoding': 'chunked'}
167 if self.token:
168 headers['X-Auth-Token'] = self.token
169
170 conn = put_object_connection(self.base_url, container, name, contents,
171 chunk_size, headers)
172
173 resp = conn.getresponse()
174 body = resp.read()
175
176 resp_headers = {}
177 for header, value in resp.getheaders():
178 resp_headers[header.lower()] = value
179
180 self._error_checker('PUT', None, headers, contents, resp, body)
JordanPa84dde32014-11-14 15:47:42 +0100181 self.expected_success(201, resp.status)
Daisuke Morita02f840b2014-03-19 20:51:01 +0900182 return resp.status, resp.reason, resp_headers
183
harika-vakadi1a9ad612012-12-14 19:12:08 +0530184
Eiichi Aikawaca3d9bd2014-03-06 15:06:21 +0900185class ObjectClientCustomizedHeader(rest_client.RestClient):
harika-vakadi1a9ad612012-12-14 19:12:08 +0530186
Andrea Frittoli8bbdb162014-01-06 11:06:13 +0000187 # TODO(andreaf) This class is now redundant, to be removed in next patch
188
189 def __init__(self, auth_provider):
190 super(ObjectClientCustomizedHeader, self).__init__(
191 auth_provider)
Eiichi Aikawaca3d9bd2014-03-06 15:06:21 +0900192 # Overwrites json-specific header encoding in rest_client.RestClient
Matthew Treinish684d8992014-01-30 16:27:40 +0000193 self.service = CONF.object_storage.catalog_type
harika-vakadi1a9ad612012-12-14 19:12:08 +0530194 self.format = 'json'
195
Sergey Murashov4fccd322014-03-22 09:58:52 +0400196 def request(self, method, url, extra_headers=False, headers=None,
197 body=None):
harika-vakadi1a9ad612012-12-14 19:12:08 +0530198 """A simple HTTP request interface."""
Matthew Treinish684d8992014-01-30 16:27:40 +0000199 dscv = CONF.identity.disable_ssl_certificate_validation
Mate Lakat23a58a32013-08-23 02:06:22 +0100200 self.http_obj = http.ClosingHttp(
201 disable_ssl_certificate_validation=dscv)
harika-vakadi1a9ad612012-12-14 19:12:08 +0530202 if headers is None:
203 headers = {}
Sergey Murashov4fccd322014-03-22 09:58:52 +0400204 elif extra_headers:
205 try:
206 headers.update(self.get_headers())
207 except (ValueError, TypeError):
208 headers = {}
harika-vakadi1a9ad612012-12-14 19:12:08 +0530209
Andrea Frittoli8bbdb162014-01-06 11:06:13 +0000210 # Authorize the request
211 req_url, req_headers, req_body = self.auth_provider.auth_request(
212 method=method, url=url, headers=headers, body=body,
213 filters=self.filters
214 )
215 # Use original method
harika-vakadi1a9ad612012-12-14 19:12:08 +0530216 resp, resp_body = self.http_obj.request(req_url, method,
Andrea Frittoli8bbdb162014-01-06 11:06:13 +0000217 headers=req_headers,
218 body=req_body)
Sean Dague89a85912014-03-19 16:37:29 -0400219 self._log_request(method, req_url, resp)
harika-vakadi6ab397b2012-12-20 12:16:17 +0530220 if resp.status == 401 or resp.status == 403:
harika-vakadi6ab397b2012-12-20 12:16:17 +0530221 raise exceptions.Unauthorized()
222
harika-vakadi1a9ad612012-12-14 19:12:08 +0530223 return resp, resp_body
224
225 def get_object(self, container, object_name, metadata=None):
226 """Retrieve object's data."""
227 headers = {}
228 if metadata:
229 for key in metadata:
230 headers[str(key)] = metadata[key]
231
232 url = "{0}/{1}".format(container, object_name)
233 resp, body = self.get(url, headers=headers)
JordanPa84dde32014-11-14 15:47:42 +0100234 self.expected_success(200, resp.status)
harika-vakadi1a9ad612012-12-14 19:12:08 +0530235 return resp, body
harika-vakadi6ab397b2012-12-20 12:16:17 +0530236
237 def create_object(self, container, object_name, data, metadata=None):
Sean Daguef237ccb2013-01-04 15:19:14 -0500238 """Create storage object."""
harika-vakadi6ab397b2012-12-20 12:16:17 +0530239
240 headers = {}
241 if metadata:
242 for key in metadata:
243 headers[str(key)] = metadata[key]
244
Martina Kollarova03720a52013-06-18 15:08:46 +0200245 if not data:
246 headers['content-length'] = '0'
harika-vakadi6ab397b2012-12-20 12:16:17 +0530247 url = "%s/%s" % (str(container), str(object_name))
248 resp, body = self.put(url, data, headers=headers)
JordanPa84dde32014-11-14 15:47:42 +0100249 self.expected_success(201, resp.status)
harika-vakadi6ab397b2012-12-20 12:16:17 +0530250 return resp, body
251
harika-vakadi2daed0a2013-01-01 20:51:39 +0530252 def delete_object(self, container, object_name, metadata=None):
Sean Daguef237ccb2013-01-04 15:19:14 -0500253 """Delete storage object."""
harika-vakadi6ab397b2012-12-20 12:16:17 +0530254
harika-vakadi2daed0a2013-01-01 20:51:39 +0530255 headers = {}
256 if metadata:
257 for key in metadata:
258 headers[str(key)] = metadata[key]
259
harika-vakadi6ab397b2012-12-20 12:16:17 +0530260 url = "%s/%s" % (str(container), str(object_name))
harika-vakadi2daed0a2013-01-01 20:51:39 +0530261 resp, body = self.delete(url, headers=headers)
JordanPa84dde32014-11-14 15:47:42 +0100262 self.expected_success(200, resp.status)
harika-vakadi6ab397b2012-12-20 12:16:17 +0530263 return resp, body
Daisuke Morita02f840b2014-03-19 20:51:01 +0900264
265 def create_object_continue(self, container, object_name,
266 data, metadata=None):
267 """Create storage object."""
268 headers = {}
269 if metadata:
270 for key in metadata:
271 headers[str(key)] = metadata[key]
272
273 if not data:
274 headers['content-length'] = '0'
275
276 if self.base_url is None:
277 self._set_auth()
278 headers['X-Auth-Token'] = self.token
279
280 conn = put_object_connection(self.base_url, str(container),
281 str(object_name), data, None, headers)
282
283 response = conn.response_class(conn.sock,
284 strict=conn.strict,
285 method=conn._method)
286 version, status, reason = response._read_status()
287 resp = {'version': version,
288 'status': str(status),
289 'reason': reason}
290
291 return resp
292
293
294def put_object_connection(base_url, container, name, contents=None,
295 chunk_size=65536, headers=None, query_string=None):
296 """
297 Helper function to make connection to put object with httplib
298 :param base_url: base_url of an object client
299 :param container: container name that the object is in
300 :param name: object name to put
301 :param contents: a string or a file like object to read object data
302 from; if None, a zero-byte put will be done
303 :param chunk_size: chunk size of data to write; it defaults to 65536;
304 used only if the the contents object has a 'read'
305 method, eg. file-like objects, ignored otherwise
306 :param headers: additional headers to include in the request, if any
307 :param query_string: if set will be appended with '?' to generated path
308 """
309 parsed = urlparse.urlparse(base_url)
310 if parsed.scheme == 'https':
311 conn = httplib.HTTPSConnection(parsed.netloc)
312 else:
313 conn = httplib.HTTPConnection(parsed.netloc)
314 path = str(parsed.path) + "/"
315 path += "%s/%s" % (str(container), str(name))
316
317 if query_string:
318 path += '?' + query_string
319 if headers:
320 headers = dict(headers)
321 else:
322 headers = {}
323 if hasattr(contents, 'read'):
324 conn.putrequest('PUT', path)
325 for header, value in headers.iteritems():
326 conn.putheader(header, value)
327 if 'Content-Length' not in headers:
328 if 'Transfer-Encoding' not in headers:
329 conn.putheader('Transfer-Encoding', 'chunked')
330 conn.endheaders()
331 chunk = contents.read(chunk_size)
332 while chunk:
333 conn.send('%x\r\n%s\r\n' % (len(chunk), chunk))
334 chunk = contents.read(chunk_size)
335 conn.send('0\r\n\r\n')
336 else:
337 conn.endheaders()
338 left = headers['Content-Length']
339 while left > 0:
340 size = chunk_size
341 if size > left:
342 size = left
343 chunk = contents.read(size)
344 conn.send(chunk)
345 left -= len(chunk)
346 else:
347 conn.request('PUT', path, contents, headers)
348
349 return conn