blob: b2f82052910acbcba56620571489cd79a1dcbdf0 [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)
dwalleck5d734432012-10-04 01:11:47 -050049 return resp, body
50
51 def update_object(self, container, object_name, data):
Sean Daguef237ccb2013-01-04 15:19:14 -050052 """Upload data to replace current storage object."""
Attila Fazekasfa756cb2013-02-12 10:52:42 +010053 return self.create_object(container, object_name, data)
dwalleck5d734432012-10-04 01:11:47 -050054
Daisuke Morita2c87d372013-10-31 19:39:19 +090055 def delete_object(self, container, object_name, params=None):
Sean Daguef237ccb2013-01-04 15:19:14 -050056 """Delete storage object."""
dwalleck5d734432012-10-04 01:11:47 -050057 url = "%s/%s" % (str(container), str(object_name))
Daisuke Morita2c87d372013-10-31 19:39:19 +090058 if params:
59 url += '?%s' % urllib.urlencode(params)
vponomaryov67b58fe2014-02-06 19:05:41 +020060 resp, body = self.delete(url, headers={})
dwalleck5d734432012-10-04 01:11:47 -050061 return resp, body
62
63 def update_object_metadata(self, container, object_name, metadata,
64 metadata_prefix='X-Object-Meta-'):
Sean Daguef237ccb2013-01-04 15:19:14 -050065 """Add, remove, or change X-Object-Meta metadata for storage object."""
dwalleck5d734432012-10-04 01:11:47 -050066
67 headers = {}
68 for key in metadata:
69 headers["%s%s" % (str(metadata_prefix), str(key))] = metadata[key]
70
71 url = "%s/%s" % (str(container), str(object_name))
72 resp, body = self.post(url, None, headers=headers)
73 return resp, body
74
75 def list_object_metadata(self, container, object_name):
Sean Daguef237ccb2013-01-04 15:19:14 -050076 """List all storage object X-Object-Meta- metadata."""
dwalleck5d734432012-10-04 01:11:47 -050077
78 url = "%s/%s" % (str(container), str(object_name))
79 resp, body = self.head(url)
80 return resp, body
Larisa Ustalov6c3c7802012-11-05 12:25:19 +020081
Daisuke Morita6d502682014-03-19 21:08:54 +090082 def get_object(self, container, object_name, metadata=None):
Larisa Ustalov6c3c7802012-11-05 12:25:19 +020083 """Retrieve object's data."""
84
Daisuke Morita6d502682014-03-19 21:08:54 +090085 headers = {}
86 if metadata:
87 for key in metadata:
88 headers[str(key)] = metadata[key]
89
Larisa Ustalov6c3c7802012-11-05 12:25:19 +020090 url = "{0}/{1}".format(container, object_name)
Daisuke Morita6d502682014-03-19 21:08:54 +090091 resp, body = self.get(url, headers=headers)
Larisa Ustalov6c3c7802012-11-05 12:25:19 +020092 return resp, body
93
rajalakshmi-ganesan925e2392012-11-30 19:17:24 +053094 def copy_object_in_same_container(self, container, src_object_name,
95 dest_object_name, metadata=None):
Sean Daguef237ccb2013-01-04 15:19:14 -050096 """Copy storage object's data to the new object using PUT."""
Larisa Ustalov6c3c7802012-11-05 12:25:19 +020097
98 url = "{0}/{1}".format(container, dest_object_name)
99 headers = {}
100 headers['X-Copy-From'] = "%s/%s" % (str(container),
101 str(src_object_name))
102 headers['content-length'] = '0'
103 if metadata:
104 for key in metadata:
105 headers[str(key)] = metadata[key]
106
107 resp, body = self.put(url, None, headers=headers)
108 return resp, body
109
rajalakshmi-ganesan925e2392012-11-30 19:17:24 +0530110 def copy_object_across_containers(self, src_container, src_object_name,
111 dst_container, dst_object_name,
112 metadata=None):
Sean Daguef237ccb2013-01-04 15:19:14 -0500113 """Copy storage object's data to the new object using PUT."""
rajalakshmi-ganesan925e2392012-11-30 19:17:24 +0530114
115 url = "{0}/{1}".format(dst_container, dst_object_name)
116 headers = {}
117 headers['X-Copy-From'] = "%s/%s" % (str(src_container),
118 str(src_object_name))
119 headers['content-length'] = '0'
120 if metadata:
121 for key in metadata:
122 headers[str(key)] = metadata[key]
123
124 resp, body = self.put(url, None, headers=headers)
125 return resp, body
126
Larisa Ustalov6c3c7802012-11-05 12:25:19 +0200127 def copy_object_2d_way(self, container, src_object_name, dest_object_name,
128 metadata=None):
Sean Daguef237ccb2013-01-04 15:19:14 -0500129 """Copy storage object's data to the new object using COPY."""
Larisa Ustalov6c3c7802012-11-05 12:25:19 +0200130
131 url = "{0}/{1}".format(container, src_object_name)
132 headers = {}
133 headers['Destination'] = "%s/%s" % (str(container),
134 str(dest_object_name))
135 if metadata:
136 for key in metadata:
137 headers[str(key)] = metadata[key]
138
139 resp, body = self.copy(url, headers=headers)
140 return resp, body
harika-vakadi1a9ad612012-12-14 19:12:08 +0530141
harika-vakadi7cfc5182013-01-16 13:59:25 +0530142 def create_object_segments(self, container, object_name, segment, data):
143 """Creates object segments."""
144 url = "{0}/{1}/{2}".format(container, object_name, segment)
Valeriy Ponomaryov88686d82014-02-16 12:24:51 +0200145 resp, body = self.put(url, data)
ravikumar-venkatesane43b77a2013-01-09 07:27:13 +0000146 return resp, body
147
Daisuke Morita02f840b2014-03-19 20:51:01 +0900148 def put_object_with_chunk(self, container, name, contents, chunk_size):
149 """
150 Put an object with Transfer-Encoding header
151 """
152 if self.base_url is None:
153 self._set_auth()
154
155 headers = {'Transfer-Encoding': 'chunked'}
156 if self.token:
157 headers['X-Auth-Token'] = self.token
158
159 conn = put_object_connection(self.base_url, container, name, contents,
160 chunk_size, headers)
161
162 resp = conn.getresponse()
163 body = resp.read()
164
165 resp_headers = {}
166 for header, value in resp.getheaders():
167 resp_headers[header.lower()] = value
168
169 self._error_checker('PUT', None, headers, contents, resp, body)
170
171 return resp.status, resp.reason, resp_headers
172
harika-vakadi1a9ad612012-12-14 19:12:08 +0530173
Eiichi Aikawaca3d9bd2014-03-06 15:06:21 +0900174class ObjectClientCustomizedHeader(rest_client.RestClient):
harika-vakadi1a9ad612012-12-14 19:12:08 +0530175
Andrea Frittoli8bbdb162014-01-06 11:06:13 +0000176 # TODO(andreaf) This class is now redundant, to be removed in next patch
177
178 def __init__(self, auth_provider):
179 super(ObjectClientCustomizedHeader, self).__init__(
180 auth_provider)
Eiichi Aikawaca3d9bd2014-03-06 15:06:21 +0900181 # Overwrites json-specific header encoding in rest_client.RestClient
Matthew Treinish684d8992014-01-30 16:27:40 +0000182 self.service = CONF.object_storage.catalog_type
harika-vakadi1a9ad612012-12-14 19:12:08 +0530183 self.format = 'json'
184
Sergey Murashov4fccd322014-03-22 09:58:52 +0400185 def request(self, method, url, extra_headers=False, headers=None,
186 body=None):
harika-vakadi1a9ad612012-12-14 19:12:08 +0530187 """A simple HTTP request interface."""
Matthew Treinish684d8992014-01-30 16:27:40 +0000188 dscv = CONF.identity.disable_ssl_certificate_validation
Mate Lakat23a58a32013-08-23 02:06:22 +0100189 self.http_obj = http.ClosingHttp(
190 disable_ssl_certificate_validation=dscv)
harika-vakadi1a9ad612012-12-14 19:12:08 +0530191 if headers is None:
192 headers = {}
Sergey Murashov4fccd322014-03-22 09:58:52 +0400193 elif extra_headers:
194 try:
195 headers.update(self.get_headers())
196 except (ValueError, TypeError):
197 headers = {}
harika-vakadi1a9ad612012-12-14 19:12:08 +0530198
Andrea Frittoli8bbdb162014-01-06 11:06:13 +0000199 # Authorize the request
200 req_url, req_headers, req_body = self.auth_provider.auth_request(
201 method=method, url=url, headers=headers, body=body,
202 filters=self.filters
203 )
204 # Use original method
harika-vakadi1a9ad612012-12-14 19:12:08 +0530205 resp, resp_body = self.http_obj.request(req_url, method,
Andrea Frittoli8bbdb162014-01-06 11:06:13 +0000206 headers=req_headers,
207 body=req_body)
Sean Dague89a85912014-03-19 16:37:29 -0400208 self._log_request(method, req_url, resp)
harika-vakadi6ab397b2012-12-20 12:16:17 +0530209 if resp.status == 401 or resp.status == 403:
harika-vakadi6ab397b2012-12-20 12:16:17 +0530210 raise exceptions.Unauthorized()
211
harika-vakadi1a9ad612012-12-14 19:12:08 +0530212 return resp, resp_body
213
214 def get_object(self, container, object_name, metadata=None):
215 """Retrieve object's data."""
216 headers = {}
217 if metadata:
218 for key in metadata:
219 headers[str(key)] = metadata[key]
220
221 url = "{0}/{1}".format(container, object_name)
222 resp, body = self.get(url, headers=headers)
223 return resp, body
harika-vakadi6ab397b2012-12-20 12:16:17 +0530224
225 def create_object(self, container, object_name, data, metadata=None):
Sean Daguef237ccb2013-01-04 15:19:14 -0500226 """Create storage object."""
harika-vakadi6ab397b2012-12-20 12:16:17 +0530227
228 headers = {}
229 if metadata:
230 for key in metadata:
231 headers[str(key)] = metadata[key]
232
Martina Kollarova03720a52013-06-18 15:08:46 +0200233 if not data:
234 headers['content-length'] = '0'
harika-vakadi6ab397b2012-12-20 12:16:17 +0530235 url = "%s/%s" % (str(container), str(object_name))
236 resp, body = self.put(url, data, headers=headers)
237 return resp, body
238
harika-vakadi2daed0a2013-01-01 20:51:39 +0530239 def delete_object(self, container, object_name, metadata=None):
Sean Daguef237ccb2013-01-04 15:19:14 -0500240 """Delete storage object."""
harika-vakadi6ab397b2012-12-20 12:16:17 +0530241
harika-vakadi2daed0a2013-01-01 20:51:39 +0530242 headers = {}
243 if metadata:
244 for key in metadata:
245 headers[str(key)] = metadata[key]
246
harika-vakadi6ab397b2012-12-20 12:16:17 +0530247 url = "%s/%s" % (str(container), str(object_name))
harika-vakadi2daed0a2013-01-01 20:51:39 +0530248 resp, body = self.delete(url, headers=headers)
harika-vakadi6ab397b2012-12-20 12:16:17 +0530249 return resp, body
Daisuke Morita02f840b2014-03-19 20:51:01 +0900250
251 def create_object_continue(self, container, object_name,
252 data, metadata=None):
253 """Create storage object."""
254 headers = {}
255 if metadata:
256 for key in metadata:
257 headers[str(key)] = metadata[key]
258
259 if not data:
260 headers['content-length'] = '0'
261
262 if self.base_url is None:
263 self._set_auth()
264 headers['X-Auth-Token'] = self.token
265
266 conn = put_object_connection(self.base_url, str(container),
267 str(object_name), data, None, headers)
268
269 response = conn.response_class(conn.sock,
270 strict=conn.strict,
271 method=conn._method)
272 version, status, reason = response._read_status()
273 resp = {'version': version,
274 'status': str(status),
275 'reason': reason}
276
277 return resp
278
279
280def put_object_connection(base_url, container, name, contents=None,
281 chunk_size=65536, headers=None, query_string=None):
282 """
283 Helper function to make connection to put object with httplib
284 :param base_url: base_url of an object client
285 :param container: container name that the object is in
286 :param name: object name to put
287 :param contents: a string or a file like object to read object data
288 from; if None, a zero-byte put will be done
289 :param chunk_size: chunk size of data to write; it defaults to 65536;
290 used only if the the contents object has a 'read'
291 method, eg. file-like objects, ignored otherwise
292 :param headers: additional headers to include in the request, if any
293 :param query_string: if set will be appended with '?' to generated path
294 """
295 parsed = urlparse.urlparse(base_url)
296 if parsed.scheme == 'https':
297 conn = httplib.HTTPSConnection(parsed.netloc)
298 else:
299 conn = httplib.HTTPConnection(parsed.netloc)
300 path = str(parsed.path) + "/"
301 path += "%s/%s" % (str(container), str(name))
302
303 if query_string:
304 path += '?' + query_string
305 if headers:
306 headers = dict(headers)
307 else:
308 headers = {}
309 if hasattr(contents, 'read'):
310 conn.putrequest('PUT', path)
311 for header, value in headers.iteritems():
312 conn.putheader(header, value)
313 if 'Content-Length' not in headers:
314 if 'Transfer-Encoding' not in headers:
315 conn.putheader('Transfer-Encoding', 'chunked')
316 conn.endheaders()
317 chunk = contents.read(chunk_size)
318 while chunk:
319 conn.send('%x\r\n%s\r\n' % (len(chunk), chunk))
320 chunk = contents.read(chunk_size)
321 conn.send('0\r\n\r\n')
322 else:
323 conn.endheaders()
324 left = headers['Content-Length']
325 while left > 0:
326 size = chunk_size
327 if size > left:
328 size = left
329 chunk = contents.read(size)
330 conn.send(chunk)
331 left -= len(chunk)
332 else:
333 conn.request('PUT', path, contents, headers)
334
335 return conn