Skip to content

Commit 31018bf

Browse files
Dean TroyerSteve Martinelli
authored andcommitted
Move object-store commands to low-level API
api.object_store.APIv1 now contains the formerly top-level functions implementing the object-store REST client. This replaces the old-style ObjectClientv1 that is no longer necessary. Change-Id: I7d8fea326b214481e7d6b24119bd41777c6aa968
1 parent e3b9b96 commit 31018bf

13 files changed

Lines changed: 773 additions & 1066 deletions

File tree

Lines changed: 381 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,381 @@
1+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
2+
# not use this file except in compliance with the License. You may obtain
3+
# a copy of the License at
4+
#
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10+
# License for the specific language governing permissions and limitations
11+
# under the License.
12+
#
13+
14+
"""Object Store v1 API Library"""
15+
16+
import os
17+
import six
18+
19+
try:
20+
from urllib.parse import urlparse # noqa
21+
except ImportError:
22+
from urlparse import urlparse # noqa
23+
24+
from openstackclient.api import api
25+
26+
27+
class APIv1(api.BaseAPI):
28+
"""Object Store v1 API"""
29+
30+
def __init__(self, **kwargs):
31+
super(APIv1, self).__init__(**kwargs)
32+
33+
def container_create(
34+
self,
35+
container=None,
36+
):
37+
"""Create a container
38+
39+
:param string container:
40+
name of container to create
41+
:returns:
42+
dict of returned headers
43+
"""
44+
45+
response = self.create(container, method='PUT')
46+
url_parts = urlparse(self.endpoint)
47+
data = {
48+
'account': url_parts.path.split('/')[-1],
49+
'container': container,
50+
'x-trans-id': response.headers.get('x-trans-id', None),
51+
}
52+
53+
return data
54+
55+
def container_delete(
56+
self,
57+
container=None,
58+
):
59+
"""Delete a container
60+
61+
:param string container:
62+
name of container to delete
63+
"""
64+
65+
if container:
66+
self.delete(container)
67+
68+
def container_list(
69+
self,
70+
all_data=False,
71+
limit=None,
72+
marker=None,
73+
end_marker=None,
74+
prefix=None,
75+
**params
76+
):
77+
"""Get containers in an account
78+
79+
:param boolean all_data:
80+
if True, return a full listing, else returns a max of
81+
10000 listings
82+
:param integer limit:
83+
query return count limit
84+
:param string marker:
85+
query marker
86+
:param string end_marker:
87+
query end_marker
88+
:param string prefix:
89+
query prefix
90+
:returns:
91+
list of container names
92+
"""
93+
94+
params['format'] = 'json'
95+
96+
if all_data:
97+
data = listing = self.container_list(
98+
limit=limit,
99+
marker=marker,
100+
end_marker=end_marker,
101+
prefix=prefix,
102+
**params
103+
)
104+
while listing:
105+
marker = listing[-1]['name']
106+
listing = self.container_list(
107+
limit=limit,
108+
marker=marker,
109+
end_marker=end_marker,
110+
prefix=prefix,
111+
**params
112+
)
113+
if listing:
114+
data.extend(listing)
115+
return data
116+
117+
if limit:
118+
params['limit'] = limit
119+
if marker:
120+
params['marker'] = marker
121+
if end_marker:
122+
params['end_marker'] = end_marker
123+
if prefix:
124+
params['prefix'] = prefix
125+
126+
return self.list('', **params)
127+
128+
def container_save(
129+
self,
130+
container=None,
131+
):
132+
"""Save all the content from a container
133+
134+
:param string container:
135+
name of container to save
136+
"""
137+
138+
objects = self.object_list(container=container)
139+
for object in objects:
140+
self.object_save(container=container, object=object['name'])
141+
142+
def container_show(
143+
self,
144+
container=None,
145+
):
146+
"""Get container details
147+
148+
:param string container:
149+
name of container to show
150+
:returns:
151+
dict of returned headers
152+
"""
153+
154+
response = self._request('HEAD', container)
155+
data = {
156+
'account': response.headers.get('x-container-meta-owner', None),
157+
'container': container,
158+
'object_count': response.headers.get(
159+
'x-container-object-count',
160+
None,
161+
),
162+
'bytes_used': response.headers.get('x-container-bytes-used', None),
163+
'read_acl': response.headers.get('x-container-read', None),
164+
'write_acl': response.headers.get('x-container-write', None),
165+
'sync_to': response.headers.get('x-container-sync-to', None),
166+
'sync_key': response.headers.get('x-container-sync-key', None),
167+
}
168+
return data
169+
170+
def object_create(
171+
self,
172+
container=None,
173+
object=None,
174+
):
175+
"""Create an object inside a container
176+
177+
:param string container:
178+
name of container to store object
179+
:param string object:
180+
local path to object
181+
:returns:
182+
dict of returned headers
183+
"""
184+
185+
if container is None or object is None:
186+
# TODO(dtroyer): What exception to raise here?
187+
return {}
188+
189+
full_url = "%s/%s" % (container, object)
190+
response = self.create(full_url, method='PUT', data=open(object))
191+
url_parts = urlparse(self.endpoint)
192+
data = {
193+
'account': url_parts.path.split('/')[-1],
194+
'container': container,
195+
'object': object,
196+
'x-trans-id': response.headers.get('X-Trans-Id', None),
197+
'etag': response.headers.get('Etag', None),
198+
}
199+
200+
return data
201+
202+
def object_delete(
203+
self,
204+
container=None,
205+
object=None,
206+
):
207+
"""Delete an object from a container
208+
209+
:param string container:
210+
name of container that stores object
211+
:param string object:
212+
name of object to delete
213+
"""
214+
215+
if container is None or object is None:
216+
return
217+
218+
self.delete("%s/%s" % (container, object))
219+
220+
def object_list(
221+
self,
222+
container=None,
223+
all_data=False,
224+
limit=None,
225+
marker=None,
226+
end_marker=None,
227+
delimiter=None,
228+
prefix=None,
229+
**params
230+
):
231+
"""List objects in a container
232+
233+
:param string container:
234+
container name to get a listing for
235+
:param boolean all_data:
236+
if True, return a full listing, else returns a max of
237+
10000 listings
238+
:param integer limit:
239+
query return count limit
240+
:param string marker:
241+
query marker
242+
:param string end_marker:
243+
query end_marker
244+
:param string prefix:
245+
query prefix
246+
:param string delimiter:
247+
string to delimit the queries on
248+
:returns: a tuple of (response headers, a list of objects) The response
249+
headers will be a dict and all header names will be lowercase.
250+
"""
251+
252+
if container is None or object is None:
253+
return None
254+
255+
if all_data:
256+
data = listing = self.object_list(
257+
container=container,
258+
limit=limit,
259+
marker=marker,
260+
end_marker=end_marker,
261+
prefix=prefix,
262+
delimiter=delimiter,
263+
**params
264+
)
265+
while listing:
266+
if delimiter:
267+
marker = listing[-1].get('name', listing[-1].get('subdir'))
268+
else:
269+
marker = listing[-1]['name']
270+
listing = self.object_list(
271+
container=container,
272+
limit=limit,
273+
marker=marker,
274+
end_marker=end_marker,
275+
prefix=prefix,
276+
delimiter=delimiter,
277+
**params
278+
)
279+
if listing:
280+
data.extend(listing)
281+
return data
282+
283+
params = {}
284+
if limit:
285+
params['limit'] = limit
286+
if marker:
287+
params['marker'] = marker
288+
if end_marker:
289+
params['end_marker'] = end_marker
290+
if prefix:
291+
params['prefix'] = prefix
292+
if delimiter:
293+
params['delimiter'] = delimiter
294+
295+
return self.list(container, **params)
296+
297+
def object_save(
298+
self,
299+
container=None,
300+
object=None,
301+
file=None,
302+
):
303+
"""Save an object stored in a container
304+
305+
:param string container:
306+
name of container that stores object
307+
:param string object:
308+
name of object to save
309+
:param string file:
310+
local name of object
311+
"""
312+
313+
if not file:
314+
file = object
315+
316+
response = self._request(
317+
'GET',
318+
"%s/%s" % (container, object),
319+
stream=True,
320+
)
321+
if response.status_code == 200:
322+
if not os.path.exists(os.path.dirname(file)):
323+
os.makedirs(os.path.dirname(file))
324+
with open(file, 'wb') as f:
325+
for chunk in response.iter_content():
326+
f.write(chunk)
327+
328+
def object_show(
329+
self,
330+
container=None,
331+
object=None,
332+
):
333+
"""Get object details
334+
335+
:param string container:
336+
container name for object to get
337+
:param string object:
338+
name of object to get
339+
:returns:
340+
dict of object properties
341+
"""
342+
343+
if container is None or object is None:
344+
return {}
345+
346+
response = self._request('HEAD', "%s/%s" % (container, object))
347+
data = {
348+
'account': response.headers.get('x-container-meta-owner', None),
349+
'container': container,
350+
'object': object,
351+
'content-type': response.headers.get('content-type', None),
352+
}
353+
if 'content-length' in response.headers:
354+
data['content-length'] = response.headers.get(
355+
'content-length',
356+
None,
357+
)
358+
if 'last-modified' in response.headers:
359+
data['last-modified'] = response.headers.get('last-modified', None)
360+
if 'etag' in response.headers:
361+
data['etag'] = response.headers.get('etag', None)
362+
if 'x-object-manifest' in response.headers:
363+
data['x-object-manifest'] = response.headers.get(
364+
'x-object-manifest',
365+
None,
366+
)
367+
for key, value in six.iteritems(response.headers):
368+
if key.startswith('x-object-meta-'):
369+
data[key[len('x-object-meta-'):].lower()] = value
370+
elif key not in (
371+
'content-type',
372+
'content-length',
373+
'last-modified',
374+
'etag',
375+
'date',
376+
'x-object-manifest',
377+
'x-container-meta-owner',
378+
):
379+
data[key.lower()] = value
380+
381+
return data

0 commit comments

Comments
 (0)