python-openstackclient/openstackclient/api/object_store_v1.py
Dean Troyer bcf4b3caec Update use of open() in object API
* Switch to use io.open() for py3 compatibility and simpler testing.
* Open files in 'rb' mode to avoid translation on Windows

Previously tests simply relied on files that were present in the
repository to run tests using open().  Change the filenames to ensure
that no longer happens.

requests_mock doesn't have a way to match against the request body for
PUT/POST; an attempt to add a new Matcher to do that worked but it
needs to subclass the currently private adapter._Matcher class or
duplicate most of its functionality.

Change-Id: I8c30b41db20af8ecafe67e760e872fc08adec905
2014-10-13 10:34:11 -05:00

389 lines
11 KiB
Python

# 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"""
import io
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)
with io.open(object, 'rb') as f:
response = self.create(
full_url,
method='PUT',
data=f,
)
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
params['format'] = 'json'
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)):
if len(os.path.dirname(file)) > 0:
os.makedirs(os.path.dirname(file))
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