2014-09-18 00:54:52 -05:00
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
|
|
# not use this file except in compliance with the License. You may obtain
|
|
|
|
# a copy of the License at
|
|
|
|
#
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
#
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
|
|
# License for the specific language governing permissions and limitations
|
|
|
|
# under the License.
|
|
|
|
#
|
|
|
|
|
|
|
|
"""Object Store v1 API Library"""
|
|
|
|
|
2014-10-08 23:22:24 -05:00
|
|
|
import io
|
2014-09-18 00:54:52 -05:00
|
|
|
import os
|
|
|
|
import six
|
|
|
|
|
|
|
|
try:
|
|
|
|
from urllib.parse import urlparse # noqa
|
|
|
|
except ImportError:
|
|
|
|
from urlparse import urlparse # noqa
|
|
|
|
|
|
|
|
from openstackclient.api import api
|
|
|
|
|
|
|
|
|
|
|
|
class APIv1(api.BaseAPI):
|
|
|
|
"""Object Store v1 API"""
|
|
|
|
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
super(APIv1, self).__init__(**kwargs)
|
|
|
|
|
|
|
|
def container_create(
|
|
|
|
self,
|
|
|
|
container=None,
|
|
|
|
):
|
|
|
|
"""Create a container
|
|
|
|
|
|
|
|
:param string container:
|
|
|
|
name of container to create
|
|
|
|
:returns:
|
|
|
|
dict of returned headers
|
|
|
|
"""
|
|
|
|
|
|
|
|
response = self.create(container, method='PUT')
|
|
|
|
url_parts = urlparse(self.endpoint)
|
|
|
|
data = {
|
|
|
|
'account': url_parts.path.split('/')[-1],
|
|
|
|
'container': container,
|
|
|
|
'x-trans-id': response.headers.get('x-trans-id', None),
|
|
|
|
}
|
|
|
|
|
|
|
|
return data
|
|
|
|
|
|
|
|
def container_delete(
|
|
|
|
self,
|
|
|
|
container=None,
|
|
|
|
):
|
|
|
|
"""Delete a container
|
|
|
|
|
|
|
|
:param string container:
|
|
|
|
name of container to delete
|
|
|
|
"""
|
|
|
|
|
|
|
|
if container:
|
|
|
|
self.delete(container)
|
|
|
|
|
|
|
|
def container_list(
|
|
|
|
self,
|
|
|
|
all_data=False,
|
|
|
|
limit=None,
|
|
|
|
marker=None,
|
|
|
|
end_marker=None,
|
|
|
|
prefix=None,
|
|
|
|
**params
|
|
|
|
):
|
|
|
|
"""Get containers in an account
|
|
|
|
|
|
|
|
:param boolean all_data:
|
|
|
|
if True, return a full listing, else returns a max of
|
|
|
|
10000 listings
|
|
|
|
:param integer limit:
|
|
|
|
query return count limit
|
|
|
|
:param string marker:
|
|
|
|
query marker
|
|
|
|
:param string end_marker:
|
|
|
|
query end_marker
|
|
|
|
:param string prefix:
|
|
|
|
query prefix
|
|
|
|
:returns:
|
|
|
|
list of container names
|
|
|
|
"""
|
|
|
|
|
|
|
|
params['format'] = 'json'
|
|
|
|
|
|
|
|
if all_data:
|
|
|
|
data = listing = self.container_list(
|
|
|
|
limit=limit,
|
|
|
|
marker=marker,
|
|
|
|
end_marker=end_marker,
|
|
|
|
prefix=prefix,
|
|
|
|
**params
|
|
|
|
)
|
|
|
|
while listing:
|
|
|
|
marker = listing[-1]['name']
|
|
|
|
listing = self.container_list(
|
|
|
|
limit=limit,
|
|
|
|
marker=marker,
|
|
|
|
end_marker=end_marker,
|
|
|
|
prefix=prefix,
|
|
|
|
**params
|
|
|
|
)
|
|
|
|
if listing:
|
|
|
|
data.extend(listing)
|
|
|
|
return data
|
|
|
|
|
|
|
|
if limit:
|
|
|
|
params['limit'] = limit
|
|
|
|
if marker:
|
|
|
|
params['marker'] = marker
|
|
|
|
if end_marker:
|
|
|
|
params['end_marker'] = end_marker
|
|
|
|
if prefix:
|
|
|
|
params['prefix'] = prefix
|
|
|
|
|
|
|
|
return self.list('', **params)
|
|
|
|
|
|
|
|
def container_save(
|
|
|
|
self,
|
|
|
|
container=None,
|
|
|
|
):
|
|
|
|
"""Save all the content from a container
|
|
|
|
|
|
|
|
:param string container:
|
|
|
|
name of container to save
|
|
|
|
"""
|
|
|
|
|
|
|
|
objects = self.object_list(container=container)
|
|
|
|
for object in objects:
|
|
|
|
self.object_save(container=container, object=object['name'])
|
|
|
|
|
|
|
|
def container_show(
|
|
|
|
self,
|
|
|
|
container=None,
|
|
|
|
):
|
|
|
|
"""Get container details
|
|
|
|
|
|
|
|
:param string container:
|
|
|
|
name of container to show
|
|
|
|
:returns:
|
|
|
|
dict of returned headers
|
|
|
|
"""
|
|
|
|
|
|
|
|
response = self._request('HEAD', container)
|
|
|
|
data = {
|
|
|
|
'account': response.headers.get('x-container-meta-owner', None),
|
|
|
|
'container': container,
|
|
|
|
'object_count': response.headers.get(
|
|
|
|
'x-container-object-count',
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
'bytes_used': response.headers.get('x-container-bytes-used', None),
|
|
|
|
'read_acl': response.headers.get('x-container-read', None),
|
|
|
|
'write_acl': response.headers.get('x-container-write', None),
|
|
|
|
'sync_to': response.headers.get('x-container-sync-to', None),
|
|
|
|
'sync_key': response.headers.get('x-container-sync-key', None),
|
|
|
|
}
|
|
|
|
return data
|
|
|
|
|
|
|
|
def object_create(
|
|
|
|
self,
|
|
|
|
container=None,
|
|
|
|
object=None,
|
|
|
|
):
|
|
|
|
"""Create an object inside a container
|
|
|
|
|
|
|
|
:param string container:
|
|
|
|
name of container to store object
|
|
|
|
:param string object:
|
|
|
|
local path to object
|
|
|
|
:returns:
|
|
|
|
dict of returned headers
|
|
|
|
"""
|
|
|
|
|
|
|
|
if container is None or object is None:
|
|
|
|
# TODO(dtroyer): What exception to raise here?
|
|
|
|
return {}
|
|
|
|
|
|
|
|
full_url = "%s/%s" % (container, object)
|
2014-10-08 23:22:24 -05:00
|
|
|
with io.open(object, 'rb') as f:
|
|
|
|
response = self.create(
|
|
|
|
full_url,
|
|
|
|
method='PUT',
|
|
|
|
data=f,
|
|
|
|
)
|
2014-09-18 00:54:52 -05:00
|
|
|
url_parts = urlparse(self.endpoint)
|
|
|
|
data = {
|
|
|
|
'account': url_parts.path.split('/')[-1],
|
|
|
|
'container': container,
|
|
|
|
'object': object,
|
|
|
|
'x-trans-id': response.headers.get('X-Trans-Id', None),
|
|
|
|
'etag': response.headers.get('Etag', None),
|
|
|
|
}
|
|
|
|
|
|
|
|
return data
|
|
|
|
|
|
|
|
def object_delete(
|
|
|
|
self,
|
|
|
|
container=None,
|
|
|
|
object=None,
|
|
|
|
):
|
|
|
|
"""Delete an object from a container
|
|
|
|
|
|
|
|
:param string container:
|
|
|
|
name of container that stores object
|
|
|
|
:param string object:
|
|
|
|
name of object to delete
|
|
|
|
"""
|
|
|
|
|
|
|
|
if container is None or object is None:
|
|
|
|
return
|
|
|
|
|
|
|
|
self.delete("%s/%s" % (container, object))
|
|
|
|
|
|
|
|
def object_list(
|
|
|
|
self,
|
|
|
|
container=None,
|
|
|
|
all_data=False,
|
|
|
|
limit=None,
|
|
|
|
marker=None,
|
|
|
|
end_marker=None,
|
|
|
|
delimiter=None,
|
|
|
|
prefix=None,
|
|
|
|
**params
|
|
|
|
):
|
|
|
|
"""List objects in a container
|
|
|
|
|
|
|
|
:param string container:
|
|
|
|
container name to get a listing for
|
|
|
|
:param boolean all_data:
|
|
|
|
if True, return a full listing, else returns a max of
|
|
|
|
10000 listings
|
|
|
|
:param integer limit:
|
|
|
|
query return count limit
|
|
|
|
:param string marker:
|
|
|
|
query marker
|
|
|
|
:param string end_marker:
|
|
|
|
query end_marker
|
|
|
|
:param string prefix:
|
|
|
|
query prefix
|
|
|
|
:param string delimiter:
|
|
|
|
string to delimit the queries on
|
|
|
|
:returns: a tuple of (response headers, a list of objects) The response
|
|
|
|
headers will be a dict and all header names will be lowercase.
|
|
|
|
"""
|
|
|
|
|
|
|
|
if container is None or object is None:
|
|
|
|
return None
|
|
|
|
|
2014-10-06 03:37:46 -04:00
|
|
|
params['format'] = 'json'
|
2014-09-18 00:54:52 -05:00
|
|
|
if all_data:
|
|
|
|
data = listing = self.object_list(
|
|
|
|
container=container,
|
|
|
|
limit=limit,
|
|
|
|
marker=marker,
|
|
|
|
end_marker=end_marker,
|
|
|
|
prefix=prefix,
|
|
|
|
delimiter=delimiter,
|
|
|
|
**params
|
|
|
|
)
|
|
|
|
while listing:
|
|
|
|
if delimiter:
|
|
|
|
marker = listing[-1].get('name', listing[-1].get('subdir'))
|
|
|
|
else:
|
|
|
|
marker = listing[-1]['name']
|
|
|
|
listing = self.object_list(
|
|
|
|
container=container,
|
|
|
|
limit=limit,
|
|
|
|
marker=marker,
|
|
|
|
end_marker=end_marker,
|
|
|
|
prefix=prefix,
|
|
|
|
delimiter=delimiter,
|
|
|
|
**params
|
|
|
|
)
|
|
|
|
if listing:
|
|
|
|
data.extend(listing)
|
|
|
|
return data
|
|
|
|
|
|
|
|
if limit:
|
|
|
|
params['limit'] = limit
|
|
|
|
if marker:
|
|
|
|
params['marker'] = marker
|
|
|
|
if end_marker:
|
|
|
|
params['end_marker'] = end_marker
|
|
|
|
if prefix:
|
|
|
|
params['prefix'] = prefix
|
|
|
|
if delimiter:
|
|
|
|
params['delimiter'] = delimiter
|
|
|
|
|
|
|
|
return self.list(container, **params)
|
|
|
|
|
|
|
|
def object_save(
|
|
|
|
self,
|
|
|
|
container=None,
|
|
|
|
object=None,
|
|
|
|
file=None,
|
|
|
|
):
|
|
|
|
"""Save an object stored in a container
|
|
|
|
|
|
|
|
:param string container:
|
|
|
|
name of container that stores object
|
|
|
|
:param string object:
|
|
|
|
name of object to save
|
|
|
|
:param string file:
|
|
|
|
local name of object
|
|
|
|
"""
|
|
|
|
|
|
|
|
if not file:
|
|
|
|
file = object
|
|
|
|
|
|
|
|
response = self._request(
|
|
|
|
'GET',
|
|
|
|
"%s/%s" % (container, object),
|
|
|
|
stream=True,
|
|
|
|
)
|
|
|
|
if response.status_code == 200:
|
|
|
|
if not os.path.exists(os.path.dirname(file)):
|
2014-10-06 03:37:46 -04:00
|
|
|
if len(os.path.dirname(file)) > 0:
|
|
|
|
os.makedirs(os.path.dirname(file))
|
2014-09-18 00:54:52 -05:00
|
|
|
with open(file, 'wb') as f:
|
|
|
|
for chunk in response.iter_content():
|
|
|
|
f.write(chunk)
|
|
|
|
|
|
|
|
def object_show(
|
|
|
|
self,
|
|
|
|
container=None,
|
|
|
|
object=None,
|
|
|
|
):
|
|
|
|
"""Get object details
|
|
|
|
|
|
|
|
:param string container:
|
|
|
|
container name for object to get
|
|
|
|
:param string object:
|
|
|
|
name of object to get
|
|
|
|
:returns:
|
|
|
|
dict of object properties
|
|
|
|
"""
|
|
|
|
|
|
|
|
if container is None or object is None:
|
|
|
|
return {}
|
|
|
|
|
|
|
|
response = self._request('HEAD', "%s/%s" % (container, object))
|
|
|
|
data = {
|
|
|
|
'account': response.headers.get('x-container-meta-owner', None),
|
|
|
|
'container': container,
|
|
|
|
'object': object,
|
|
|
|
'content-type': response.headers.get('content-type', None),
|
|
|
|
}
|
|
|
|
if 'content-length' in response.headers:
|
|
|
|
data['content-length'] = response.headers.get(
|
|
|
|
'content-length',
|
|
|
|
None,
|
|
|
|
)
|
|
|
|
if 'last-modified' in response.headers:
|
|
|
|
data['last-modified'] = response.headers.get('last-modified', None)
|
|
|
|
if 'etag' in response.headers:
|
|
|
|
data['etag'] = response.headers.get('etag', None)
|
|
|
|
if 'x-object-manifest' in response.headers:
|
|
|
|
data['x-object-manifest'] = response.headers.get(
|
|
|
|
'x-object-manifest',
|
|
|
|
None,
|
|
|
|
)
|
|
|
|
for key, value in six.iteritems(response.headers):
|
|
|
|
if key.startswith('x-object-meta-'):
|
|
|
|
data[key[len('x-object-meta-'):].lower()] = value
|
|
|
|
elif key not in (
|
|
|
|
'content-type',
|
|
|
|
'content-length',
|
|
|
|
'last-modified',
|
|
|
|
'etag',
|
|
|
|
'date',
|
|
|
|
'x-object-manifest',
|
|
|
|
'x-container-meta-owner',
|
|
|
|
):
|
|
|
|
data[key.lower()] = value
|
|
|
|
|
|
|
|
return data
|