Adopting change 151897 (https://review.openstack.org/#/c/151897/), previously abandoned.
This is in order to support a tool that will synchronize the names on the HPSS filesystem to the names in Swift metadata.
This commit is contained in:
parent
14599e460b
commit
2d4cc93deb
@ -218,38 +218,6 @@ def clean_metadata(path_or_fd):
|
|||||||
key += 1
|
key += 1
|
||||||
|
|
||||||
|
|
||||||
def validate_object(metadata, statinfo=None):
|
|
||||||
if not metadata:
|
|
||||||
return False
|
|
||||||
|
|
||||||
if X_TIMESTAMP not in metadata.keys() or \
|
|
||||||
X_CONTENT_TYPE not in metadata.keys() or \
|
|
||||||
X_ETAG not in metadata.keys() or \
|
|
||||||
X_CONTENT_LENGTH not in metadata.keys() or \
|
|
||||||
X_TYPE not in metadata.keys() or \
|
|
||||||
X_OBJECT_TYPE not in metadata.keys():
|
|
||||||
return False
|
|
||||||
|
|
||||||
if statinfo and stat.S_ISREG(statinfo.st_mode):
|
|
||||||
|
|
||||||
# File length has changed
|
|
||||||
if int(metadata[X_CONTENT_LENGTH]) != statinfo.st_size:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# File might have changed with length being the same.
|
|
||||||
if X_MTIME in metadata and \
|
|
||||||
normalize_timestamp(metadata[X_MTIME]) != \
|
|
||||||
normalize_timestamp(statinfo.st_mtime):
|
|
||||||
return False
|
|
||||||
|
|
||||||
if metadata[X_TYPE] == OBJECT:
|
|
||||||
return True
|
|
||||||
|
|
||||||
logging.warn('validate_object: metadata type is not OBJECT (%r)',
|
|
||||||
metadata[X_TYPE])
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def _read_for_etag(fp):
|
def _read_for_etag(fp):
|
||||||
etag = md5()
|
etag = md5()
|
||||||
while True:
|
while True:
|
||||||
@ -267,7 +235,7 @@ def _read_for_etag(fp):
|
|||||||
return etag.hexdigest()
|
return etag.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
def _get_etag(path_or_fd):
|
def get_etag(fd):
|
||||||
"""
|
"""
|
||||||
FIXME: It would be great to have a translator that returns the md5sum() of
|
FIXME: It would be great to have a translator that returns the md5sum() of
|
||||||
the file as an xattr that can be simply fetched.
|
the file as an xattr that can be simply fetched.
|
||||||
@ -275,16 +243,16 @@ def _get_etag(path_or_fd):
|
|||||||
Since we don't have that we should yield after each chunk read and
|
Since we don't have that we should yield after each chunk read and
|
||||||
computed so that we don't consume the worker thread.
|
computed so that we don't consume the worker thread.
|
||||||
"""
|
"""
|
||||||
if isinstance(path_or_fd, int):
|
if isinstance(fd, int):
|
||||||
# We are given a file descriptor, so this is an invocation from the
|
# We are given a file descriptor, so this is an invocation from the
|
||||||
# DiskFile.open() method.
|
# DiskFile.open() method.
|
||||||
fd = path_or_fd
|
fd = fd
|
||||||
etag = _read_for_etag(do_dup(fd))
|
etag = _read_for_etag(do_dup(fd))
|
||||||
do_lseek(fd, 0, os.SEEK_SET)
|
do_lseek(fd, 0, os.SEEK_SET)
|
||||||
else:
|
else:
|
||||||
# We are given a path to the object when the DiskDir.list_objects_iter
|
# We are given a path to the object when the DiskDir.list_objects_iter
|
||||||
# method invokes us.
|
# method invokes us.
|
||||||
path = path_or_fd
|
path = fd
|
||||||
fd = do_open(path, os.O_RDONLY)
|
fd = do_open(path, os.O_RDONLY)
|
||||||
etag = _read_for_etag(fd)
|
etag = _read_for_etag(fd)
|
||||||
do_close(fd)
|
do_close(fd)
|
||||||
@ -317,29 +285,10 @@ def get_object_metadata(obj_path_or_fd, stats=None):
|
|||||||
X_OBJECT_TYPE: DIR_NON_OBJECT if is_dir else FILE,
|
X_OBJECT_TYPE: DIR_NON_OBJECT if is_dir else FILE,
|
||||||
X_CONTENT_LENGTH: 0 if is_dir else stats.st_size,
|
X_CONTENT_LENGTH: 0 if is_dir else stats.st_size,
|
||||||
X_MTIME: 0 if is_dir else normalize_timestamp(stats.st_mtime),
|
X_MTIME: 0 if is_dir else normalize_timestamp(stats.st_mtime),
|
||||||
X_ETAG: md5().hexdigest() if is_dir else _get_etag(obj_path_or_fd)}
|
X_ETAG: md5().hexdigest() if is_dir else get_etag(obj_path_or_fd)}
|
||||||
return metadata
|
return metadata
|
||||||
|
|
||||||
|
|
||||||
def restore_metadata(path, metadata, meta_orig):
|
|
||||||
if meta_orig:
|
|
||||||
meta_new = meta_orig.copy()
|
|
||||||
meta_new.update(metadata)
|
|
||||||
else:
|
|
||||||
meta_new = metadata
|
|
||||||
if meta_orig != meta_new:
|
|
||||||
write_metadata(path, meta_new)
|
|
||||||
return meta_new
|
|
||||||
|
|
||||||
|
|
||||||
def create_object_metadata(obj_path_or_fd, stats=None, existing_meta={}):
|
|
||||||
# We must accept either a path or a file descriptor as an argument to this
|
|
||||||
# method, as the diskfile modules uses a file descriptior and the DiskDir
|
|
||||||
# module (for container operations) uses a path.
|
|
||||||
metadata_from_stat = get_object_metadata(obj_path_or_fd, stats)
|
|
||||||
return restore_metadata(obj_path_or_fd, metadata_from_stat, existing_meta)
|
|
||||||
|
|
||||||
|
|
||||||
# The following dir_xxx calls should definitely be replaced
|
# The following dir_xxx calls should definitely be replaced
|
||||||
# with a Metadata class to encapsulate their implementation.
|
# with a Metadata class to encapsulate their implementation.
|
||||||
# :FIXME: For now we have them as functions, but we should
|
# :FIXME: For now we have them as functions, but we should
|
||||||
|
@ -25,6 +25,7 @@ import logging
|
|||||||
import time
|
import time
|
||||||
import hpssfs
|
import hpssfs
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
from hashlib import md5
|
||||||
from eventlet import sleep
|
from eventlet import sleep
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from swiftonhpss.swift.common.exceptions import AlreadyExistsAsFile, \
|
from swiftonhpss.swift.common.exceptions import AlreadyExistsAsFile, \
|
||||||
@ -41,9 +42,9 @@ from swiftonhpss.swift.common.exceptions import SwiftOnFileSystemOSError, \
|
|||||||
from swiftonhpss.swift.common.fs_utils import do_fstat, do_open, do_close, \
|
from swiftonhpss.swift.common.fs_utils import do_fstat, do_open, do_close, \
|
||||||
do_unlink, do_chown, do_fsync, do_fchown, do_stat, do_write, do_read, \
|
do_unlink, do_chown, do_fsync, do_fchown, do_stat, do_write, do_read, \
|
||||||
do_fadvise64, do_rename, do_fdatasync, do_lseek, do_mkdir
|
do_fadvise64, do_rename, do_fdatasync, do_lseek, do_mkdir
|
||||||
from swiftonhpss.swift.common.utils import read_metadata, write_metadata, \
|
from swiftonhpss.swift.common.utils import read_metadata, write_metadata,\
|
||||||
validate_object, create_object_metadata, rmobjdir, dir_is_object, \
|
rmobjdir, dir_is_object, \
|
||||||
get_object_metadata, write_pickle
|
get_object_metadata, write_pickle, get_etag
|
||||||
from swiftonhpss.swift.common.utils import X_CONTENT_TYPE, \
|
from swiftonhpss.swift.common.utils import X_CONTENT_TYPE, \
|
||||||
X_TIMESTAMP, X_TYPE, X_OBJECT_TYPE, FILE, OBJECT, DIR_TYPE, \
|
X_TIMESTAMP, X_TYPE, X_OBJECT_TYPE, FILE, OBJECT, DIR_TYPE, \
|
||||||
FILE_TYPE, DEFAULT_UID, DEFAULT_GID, DIR_NON_OBJECT, DIR_OBJECT, \
|
FILE_TYPE, DEFAULT_UID, DEFAULT_GID, DIR_NON_OBJECT, DIR_OBJECT, \
|
||||||
@ -53,7 +54,7 @@ from swift.obj.diskfile import get_async_dir
|
|||||||
|
|
||||||
# FIXME: Hopefully we'll be able to move to Python 2.7+ where O_CLOEXEC will
|
# FIXME: Hopefully we'll be able to move to Python 2.7+ where O_CLOEXEC will
|
||||||
# be back ported. See http://www.python.org/dev/peps/pep-0433/
|
# be back ported. See http://www.python.org/dev/peps/pep-0433/
|
||||||
O_CLOEXEC = 0o2000000
|
O_CLOEXEC = 02000000
|
||||||
|
|
||||||
MAX_RENAME_ATTEMPTS = 10
|
MAX_RENAME_ATTEMPTS = 10
|
||||||
MAX_OPEN_ATTEMPTS = 10
|
MAX_OPEN_ATTEMPTS = 10
|
||||||
@ -593,7 +594,12 @@ class DiskFile(object):
|
|||||||
self._is_dir = False
|
self._is_dir = False
|
||||||
self._metadata = None
|
self._metadata = None
|
||||||
self._fd = None
|
self._fd = None
|
||||||
|
# Save stat info as internal variable to avoid multiple stat() calls
|
||||||
self._stat = None
|
self._stat = None
|
||||||
|
# Save md5sum of object as internal variable to avoid reading the
|
||||||
|
# entire object more than once.
|
||||||
|
self._etag = None
|
||||||
|
self._file_has_changed = None
|
||||||
# Don't store a value for data_file until we know it exists.
|
# Don't store a value for data_file until we know it exists.
|
||||||
self._data_file = None
|
self._data_file = None
|
||||||
|
|
||||||
@ -662,9 +668,9 @@ class DiskFile(object):
|
|||||||
obj_size = self._stat.st_size
|
obj_size = self._stat.st_size
|
||||||
|
|
||||||
self._metadata = read_metadata(self._fd)
|
self._metadata = read_metadata(self._fd)
|
||||||
if not validate_object(self._metadata, self._stat):
|
if not self._validate_object_metadata(self._fd):
|
||||||
self._metadata = create_object_metadata(self._fd, self._stat,
|
self._create_object_metadata(self._fd)
|
||||||
self._metadata)
|
|
||||||
assert self._metadata is not None
|
assert self._metadata is not None
|
||||||
self._filter_metadata()
|
self._filter_metadata()
|
||||||
|
|
||||||
@ -694,6 +700,92 @@ class DiskFile(object):
|
|||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
def _validate_object_metadata(self, fd):
|
||||||
|
|
||||||
|
# Has no Swift specific metadata saved as xattr. Probably because
|
||||||
|
# object was added/replaced through filesystem interface.
|
||||||
|
if not self._metadata:
|
||||||
|
self._file_has_changed = True
|
||||||
|
return False
|
||||||
|
|
||||||
|
required_keys = \
|
||||||
|
(X_TIMESTAMP, X_CONTENT_TYPE, X_CONTENT_LENGTH, X_ETAG,
|
||||||
|
# SOF specific keys
|
||||||
|
X_TYPE, X_OBJECT_TYPE)
|
||||||
|
|
||||||
|
if not all(k in self._metadata for k in required_keys):
|
||||||
|
# At least one of the required keys does not exist
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not self._is_dir:
|
||||||
|
# X_MTIME is a new key added recently, newer objects will
|
||||||
|
# have the key set during PUT.
|
||||||
|
if X_MTIME in self._metadata:
|
||||||
|
# Check if the file has been modified through filesystem
|
||||||
|
# interface by comparing mtime stored in xattr during PUT
|
||||||
|
# and current mtime of file.
|
||||||
|
if normalize_timestamp(self._metadata[X_MTIME]) != \
|
||||||
|
normalize_timestamp(self._stat.st_mtime):
|
||||||
|
self._file_has_changed = True
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
# Without X_MTIME key, comparing md5sum is the only way
|
||||||
|
# to determine if file has changed or not. This is inefficient
|
||||||
|
# but there's no other way!
|
||||||
|
self._etag = get_etag(fd)
|
||||||
|
if self._etag != self._metadata[X_ETAG]:
|
||||||
|
self._file_has_changed = True
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
# Checksums are same; File has not changed. For the next
|
||||||
|
# GET request on same file, we don't compute md5sum again!
|
||||||
|
# This is achieved by setting X_MTIME to mtime in
|
||||||
|
# _create_object_metadata()
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self._metadata[X_TYPE] == OBJECT:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _create_object_metadata(self, fd):
|
||||||
|
|
||||||
|
if self._etag is None:
|
||||||
|
self._etag = md5().hexdigest() if self._is_dir \
|
||||||
|
else get_etag(fd)
|
||||||
|
|
||||||
|
if self._file_has_changed or (X_TIMESTAMP not in self._metadata):
|
||||||
|
timestamp = normalize_timestamp(self._stat.st_mtime)
|
||||||
|
else:
|
||||||
|
timestamp = self._metadata[X_TIMESTAMP]
|
||||||
|
|
||||||
|
metadata = {
|
||||||
|
X_TYPE: OBJECT,
|
||||||
|
X_TIMESTAMP: timestamp,
|
||||||
|
X_CONTENT_TYPE: DIR_TYPE if self._is_dir else FILE_TYPE,
|
||||||
|
X_OBJECT_TYPE: DIR_NON_OBJECT if self._is_dir else FILE,
|
||||||
|
X_CONTENT_LENGTH: 0 if self._is_dir else self._stat.st_size,
|
||||||
|
X_ETAG: self._etag}
|
||||||
|
|
||||||
|
# Add X_MTIME key if object is a file
|
||||||
|
if not self._is_dir:
|
||||||
|
metadata[X_MTIME] = normalize_timestamp(self._stat.st_mtime)
|
||||||
|
|
||||||
|
meta_new = self._metadata.copy()
|
||||||
|
meta_new.update(metadata)
|
||||||
|
if self._metadata != meta_new:
|
||||||
|
write_metadata(fd, meta_new)
|
||||||
|
# Avoid additional read_metadata() later
|
||||||
|
self._metadata = meta_new
|
||||||
|
|
||||||
|
def _filter_metadata(self):
|
||||||
|
for key in (X_TYPE, X_OBJECT_TYPE, X_MTIME):
|
||||||
|
self._metadata.pop(key, None)
|
||||||
|
if self._file_has_changed:
|
||||||
|
# Really ugly hack to let SOF's GET() wrapper know that we need
|
||||||
|
# to update the container database
|
||||||
|
self._metadata['X-Object-Sysmeta-Update-Container'] = True
|
||||||
|
|
||||||
def _is_object_expired(self, metadata):
|
def _is_object_expired(self, metadata):
|
||||||
try:
|
try:
|
||||||
x_delete_at = int(metadata['X-Delete-At'])
|
x_delete_at = int(metadata['X-Delete-At'])
|
||||||
@ -709,12 +801,6 @@ class DiskFile(object):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _filter_metadata(self):
|
|
||||||
if X_TYPE in self._metadata:
|
|
||||||
self._metadata.pop(X_TYPE)
|
|
||||||
if X_OBJECT_TYPE in self._metadata:
|
|
||||||
self._metadata.pop(X_OBJECT_TYPE)
|
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
"""
|
"""
|
||||||
Context enter.
|
Context enter.
|
||||||
|
@ -30,7 +30,7 @@ from swift.common.swob import HTTPConflict, HTTPBadRequest, HeaderKeyDict, \
|
|||||||
from swift.common.utils import public, timing_stats, replication, \
|
from swift.common.utils import public, timing_stats, replication, \
|
||||||
config_true_value, Timestamp, csv_append
|
config_true_value, Timestamp, csv_append
|
||||||
from swift.common.request_helpers import get_name_and_placement, \
|
from swift.common.request_helpers import get_name_and_placement, \
|
||||||
split_and_validate_path, is_sys_or_user_meta
|
split_and_validate_path, is_sys_or_user_meta, is_user_meta
|
||||||
from swiftonhpss.swift.common.exceptions import AlreadyExistsAsFile, \
|
from swiftonhpss.swift.common.exceptions import AlreadyExistsAsFile, \
|
||||||
AlreadyExistsAsDir, SwiftOnFileSystemIOError, SwiftOnFileSystemOSError, \
|
AlreadyExistsAsDir, SwiftOnFileSystemIOError, SwiftOnFileSystemOSError, \
|
||||||
SwiftOnFileFsException
|
SwiftOnFileFsException
|
||||||
@ -41,6 +41,7 @@ from swift.common.constraints import valid_timestamp, check_account_format, \
|
|||||||
check_destination_header
|
check_destination_header
|
||||||
|
|
||||||
from swift.obj import server
|
from swift.obj import server
|
||||||
|
from swift.common.ring import Ring
|
||||||
|
|
||||||
from swiftonhpss.swift.obj.diskfile import DiskFileManager
|
from swiftonhpss.swift.obj.diskfile import DiskFileManager
|
||||||
from swiftonhpss.swift.common.constraints import check_object_creation
|
from swiftonhpss.swift.common.constraints import check_object_creation
|
||||||
@ -76,11 +77,19 @@ class ObjectController(server.ObjectController):
|
|||||||
"""
|
"""
|
||||||
# Replaces Swift's DiskFileRouter object reference with ours.
|
# Replaces Swift's DiskFileRouter object reference with ours.
|
||||||
self._diskfile_router = SwiftOnFileDiskFileRouter(conf, self.logger)
|
self._diskfile_router = SwiftOnFileDiskFileRouter(conf, self.logger)
|
||||||
|
self.swift_dir = conf.get('swift_dir', '/etc/swift')
|
||||||
|
self.container_ring = None
|
||||||
# This conf option will be deprecated and eventualy removed in
|
# This conf option will be deprecated and eventualy removed in
|
||||||
# future releases
|
# future releases
|
||||||
utils.read_pickled_metadata = \
|
utils.read_pickled_metadata = \
|
||||||
config_true_value(conf.get('read_pickled_metadata', 'no'))
|
config_true_value(conf.get('read_pickled_metadata', 'no'))
|
||||||
|
|
||||||
|
def get_container_ring(self):
|
||||||
|
"""Get the container ring. Load it, if it hasn't been yet."""
|
||||||
|
if not self.container_ring:
|
||||||
|
self.container_ring = Ring(self.swift_dir, ring_name='container')
|
||||||
|
return self.container_ring
|
||||||
|
|
||||||
@public
|
@public
|
||||||
@timing_stats()
|
@timing_stats()
|
||||||
def PUT(self, request):
|
def PUT(self, request):
|
||||||
@ -252,8 +261,8 @@ class ObjectController(server.ObjectController):
|
|||||||
device, policy)
|
device, policy)
|
||||||
# Create convenience symlink
|
# Create convenience symlink
|
||||||
try:
|
try:
|
||||||
self.object_symlink(request, disk_file._data_file, device,
|
self._object_symlink(request, disk_file._data_file, device,
|
||||||
account)
|
account)
|
||||||
except SwiftOnFileSystemOSError:
|
except SwiftOnFileSystemOSError:
|
||||||
return HTTPServiceUnavailable(request=request)
|
return HTTPServiceUnavailable(request=request)
|
||||||
return HTTPCreated(request=request, etag=etag)
|
return HTTPCreated(request=request, etag=etag)
|
||||||
@ -263,7 +272,39 @@ class ObjectController(server.ObjectController):
|
|||||||
split_and_validate_path(request, 1, 5, True)
|
split_and_validate_path(request, 1, 5, True)
|
||||||
return HTTPConflict(drive=device, request=request)
|
return HTTPConflict(drive=device, request=request)
|
||||||
|
|
||||||
def object_symlink(self, request, diskfile, device, account):
|
def _sof_container_update(self, request, resp):
|
||||||
|
"""
|
||||||
|
SOF specific metadata is set in DiskFile.open()._filter_metadata()
|
||||||
|
This method internally invokes Swift's container_update() method.
|
||||||
|
"""
|
||||||
|
device, partition, account, container, obj, policy_idx = \
|
||||||
|
get_name_and_placement(request, 5, 5, True)
|
||||||
|
|
||||||
|
# The container_update() method requires certain container
|
||||||
|
# specific headers. The proxy object controller appends these
|
||||||
|
# headers for PUT backend request but not for HEAD/GET requests.
|
||||||
|
# Thus, we populate the required information in request
|
||||||
|
# and then invoke container_update()
|
||||||
|
container_partition, container_nodes = \
|
||||||
|
self.get_container_ring().get_nodes(account, container)
|
||||||
|
request.headers['X-Container-Partition'] = container_partition
|
||||||
|
for node in container_nodes:
|
||||||
|
request.headers['X-Container-Host'] = csv_append(
|
||||||
|
request.headers.get('X-Container-Host'),
|
||||||
|
'%(ip)s:%(port)s' % node)
|
||||||
|
request.headers['X-Container-Device'] = csv_append(
|
||||||
|
request.headers.get('X-Container-Device'), node['device'])
|
||||||
|
|
||||||
|
self.container_update(
|
||||||
|
'PUT', account, container, obj, request,
|
||||||
|
HeaderKeyDict({
|
||||||
|
'x-size': resp.headers['Content-Length'],
|
||||||
|
'x-content-type': resp.headers['Content-Type'],
|
||||||
|
'x-timestamp': resp.headers['X-Timestamp'],
|
||||||
|
'x-etag': resp.headers['ETag']}),
|
||||||
|
device, policy_idx)
|
||||||
|
|
||||||
|
def _object_symlink(self, request, diskfile, device, account):
|
||||||
mount = diskfile.split(device)[0]
|
mount = diskfile.split(device)[0]
|
||||||
dev = "%s%s" % (mount, device)
|
dev = "%s%s" % (mount, device)
|
||||||
project = None
|
project = None
|
||||||
@ -333,22 +374,8 @@ class ObjectController(server.ObjectController):
|
|||||||
except SwiftOnFileSystemIOError:
|
except SwiftOnFileSystemIOError:
|
||||||
return HTTPServiceUnavailable(request=request)
|
return HTTPServiceUnavailable(request=request)
|
||||||
|
|
||||||
# Bill Owen's hack to force container sync on HEAD, so we can manually
|
|
||||||
# tell the Swift container server when objects exist on disk it didn't
|
|
||||||
# know about.
|
|
||||||
# TODO: do a similar trick for HEADing objects that didn't exist
|
|
||||||
# TODO: see if this block that's duplicated can be a function instead
|
|
||||||
if 'X-Object-Sysmeta-Update-Container' in response.headers:
|
if 'X-Object-Sysmeta-Update-Container' in response.headers:
|
||||||
self.container_update(
|
self._sof_container_update(request, response)
|
||||||
'PUT', account, container, obj, request,
|
|
||||||
HeaderKeyDict(
|
|
||||||
{'x-size': metadata['Content-Length'],
|
|
||||||
'x-content-type': metadata['Content-Type'],
|
|
||||||
'x-timestamp': metadata['X-Timestamp'],
|
|
||||||
'x-etag': metadata['ETag']
|
|
||||||
}
|
|
||||||
),
|
|
||||||
device, policy)
|
|
||||||
response.headers.pop('X-Object-Sysmeta-Update-Container')
|
response.headers.pop('X-Object-Sysmeta-Update-Container')
|
||||||
|
|
||||||
return response
|
return response
|
||||||
@ -551,7 +578,7 @@ class ObjectController(server.ObjectController):
|
|||||||
orig_timestamp = e.timestamp
|
orig_timestamp = e.timestamp
|
||||||
orig_metadata = e.metadata
|
orig_metadata = e.metadata
|
||||||
response_class = HTTPNotFound
|
response_class = HTTPNotFound
|
||||||
except DiskFileDeleted:
|
except DiskFileDeleted as e:
|
||||||
orig_timestamp = e.timestamp
|
orig_timestamp = e.timestamp
|
||||||
orig_metadata = {}
|
orig_metadata = {}
|
||||||
response_class = HTTPNotFound
|
response_class = HTTPNotFound
|
||||||
|
@ -317,36 +317,6 @@ class TestUtils(unittest.TestCase):
|
|||||||
assert res_d == {}
|
assert res_d == {}
|
||||||
assert _xattr_op_cnt['get'] == 3, "%r" % _xattr_op_cnt
|
assert _xattr_op_cnt['get'] == 3, "%r" % _xattr_op_cnt
|
||||||
|
|
||||||
def test_restore_metadata_none(self):
|
|
||||||
# No initial metadata
|
|
||||||
path = "/tmp/foo/i"
|
|
||||||
res_d = utils.restore_metadata(path, {'b': 'y'}, {})
|
|
||||||
expected_d = {'b': 'y'}
|
|
||||||
assert res_d == expected_d, "Expected %r, result %r" % (expected_d, res_d)
|
|
||||||
assert _xattr_op_cnt['set'] == 1, "%r" % _xattr_op_cnt
|
|
||||||
|
|
||||||
def test_restore_metadata(self):
|
|
||||||
# Initial metadata
|
|
||||||
path = "/tmp/foo/i"
|
|
||||||
initial_d = {'a': 'z'}
|
|
||||||
xkey = _xkey(path, utils.METADATA_KEY)
|
|
||||||
_xattrs[xkey] = serialize_metadata(initial_d)
|
|
||||||
res_d = utils.restore_metadata(path, {'b': 'y'}, initial_d)
|
|
||||||
expected_d = {'a': 'z', 'b': 'y'}
|
|
||||||
assert res_d == expected_d, "Expected %r, result %r" % (expected_d, res_d)
|
|
||||||
assert _xattr_op_cnt['set'] == 1, "%r" % _xattr_op_cnt
|
|
||||||
|
|
||||||
def test_restore_metadata_nochange(self):
|
|
||||||
# Initial metadata but no changes
|
|
||||||
path = "/tmp/foo/i"
|
|
||||||
initial_d = {'a': 'z'}
|
|
||||||
xkey = _xkey(path, utils.METADATA_KEY)
|
|
||||||
_xattrs[xkey] = serialize_metadata(initial_d)
|
|
||||||
res_d = utils.restore_metadata(path, {}, initial_d)
|
|
||||||
expected_d = {'a': 'z'}
|
|
||||||
assert res_d == expected_d, "Expected %r, result %r" % (expected_d, res_d)
|
|
||||||
assert _xattr_op_cnt['set'] == 0, "%r" % _xattr_op_cnt
|
|
||||||
|
|
||||||
def test_deserialize_metadata_pickle(self):
|
def test_deserialize_metadata_pickle(self):
|
||||||
orig_md = {'key1': 'value1', 'key2': 'value2'}
|
orig_md = {'key1': 'value1', 'key2': 'value2'}
|
||||||
pickled_md = pickle.dumps(orig_md, PICKLE_PROTOCOL)
|
pickled_md = pickle.dumps(orig_md, PICKLE_PROTOCOL)
|
||||||
@ -385,14 +355,14 @@ class TestUtils(unittest.TestCase):
|
|||||||
|
|
||||||
def test_get_etag_empty(self):
|
def test_get_etag_empty(self):
|
||||||
tf = tempfile.NamedTemporaryFile()
|
tf = tempfile.NamedTemporaryFile()
|
||||||
hd = utils._get_etag(tf.name)
|
hd = utils.get_etag(tf.name)
|
||||||
assert hd == hashlib.md5().hexdigest()
|
assert hd == hashlib.md5().hexdigest()
|
||||||
|
|
||||||
def test_get_etag(self):
|
def test_get_etag(self):
|
||||||
tf = tempfile.NamedTemporaryFile()
|
tf = tempfile.NamedTemporaryFile()
|
||||||
tf.file.write('123' * utils.CHUNK_SIZE)
|
tf.file.write('123' * utils.CHUNK_SIZE)
|
||||||
tf.file.flush()
|
tf.file.flush()
|
||||||
hd = utils._get_etag(tf.name)
|
hd = utils.get_etag(tf.name)
|
||||||
tf.file.seek(0)
|
tf.file.seek(0)
|
||||||
md5 = hashlib.md5()
|
md5 = hashlib.md5()
|
||||||
while True:
|
while True:
|
||||||
@ -402,137 +372,6 @@ class TestUtils(unittest.TestCase):
|
|||||||
md5.update(chunk)
|
md5.update(chunk)
|
||||||
assert hd == md5.hexdigest()
|
assert hd == md5.hexdigest()
|
||||||
|
|
||||||
def test_get_object_metadata_dne(self):
|
|
||||||
md = utils.get_object_metadata("/tmp/doesNotEx1st")
|
|
||||||
assert md == {}
|
|
||||||
|
|
||||||
def test_get_object_metadata_err(self):
|
|
||||||
tf = tempfile.NamedTemporaryFile()
|
|
||||||
try:
|
|
||||||
utils.get_object_metadata(
|
|
||||||
os.path.join(tf.name, "doesNotEx1st"))
|
|
||||||
except SwiftOnFileSystemOSError as e:
|
|
||||||
assert e.errno != errno.ENOENT
|
|
||||||
else:
|
|
||||||
self.fail("Expected exception")
|
|
||||||
|
|
||||||
obj_keys = (utils.X_TIMESTAMP, utils.X_CONTENT_TYPE, utils.X_ETAG,
|
|
||||||
utils.X_CONTENT_LENGTH, utils.X_TYPE, utils.X_OBJECT_TYPE)
|
|
||||||
|
|
||||||
def test_get_object_metadata_file(self):
|
|
||||||
tf = tempfile.NamedTemporaryFile()
|
|
||||||
tf.file.write('123')
|
|
||||||
tf.file.flush()
|
|
||||||
md = utils.get_object_metadata(tf.name)
|
|
||||||
for key in self.obj_keys:
|
|
||||||
assert key in md, "Expected key %s in %r" % (key, md)
|
|
||||||
assert md[utils.X_TYPE] == utils.OBJECT
|
|
||||||
assert md[utils.X_OBJECT_TYPE] == utils.FILE
|
|
||||||
assert md[utils.X_CONTENT_TYPE] == utils.FILE_TYPE
|
|
||||||
assert md[utils.X_CONTENT_LENGTH] == os.path.getsize(tf.name)
|
|
||||||
assert md[utils.X_TIMESTAMP] == utils.normalize_timestamp(os.path.getctime(tf.name))
|
|
||||||
assert md[utils.X_ETAG] == utils._get_etag(tf.name)
|
|
||||||
|
|
||||||
def test_get_object_metadata_dir(self):
|
|
||||||
td = tempfile.mkdtemp()
|
|
||||||
try:
|
|
||||||
md = utils.get_object_metadata(td)
|
|
||||||
for key in self.obj_keys:
|
|
||||||
assert key in md, "Expected key %s in %r" % (key, md)
|
|
||||||
assert md[utils.X_TYPE] == utils.OBJECT
|
|
||||||
assert md[utils.X_OBJECT_TYPE] == utils.DIR_NON_OBJECT
|
|
||||||
assert md[utils.X_CONTENT_TYPE] == utils.DIR_TYPE
|
|
||||||
assert md[utils.X_CONTENT_LENGTH] == 0
|
|
||||||
assert md[utils.X_TIMESTAMP] == utils.normalize_timestamp(os.path.getctime(td))
|
|
||||||
assert md[utils.X_ETAG] == hashlib.md5().hexdigest()
|
|
||||||
finally:
|
|
||||||
os.rmdir(td)
|
|
||||||
|
|
||||||
def test_create_object_metadata_file(self):
|
|
||||||
tf = tempfile.NamedTemporaryFile()
|
|
||||||
tf.file.write('4567')
|
|
||||||
tf.file.flush()
|
|
||||||
r_md = utils.create_object_metadata(tf.name)
|
|
||||||
|
|
||||||
xkey = _xkey(tf.name, utils.METADATA_KEY)
|
|
||||||
assert len(_xattrs.keys()) == 1
|
|
||||||
assert xkey in _xattrs
|
|
||||||
assert _xattr_op_cnt['set'] == 1
|
|
||||||
md = deserialize_metadata(_xattrs[xkey])
|
|
||||||
assert r_md == md
|
|
||||||
|
|
||||||
for key in self.obj_keys:
|
|
||||||
assert key in md, "Expected key %s in %r" % (key, md)
|
|
||||||
assert md[utils.X_TYPE] == utils.OBJECT
|
|
||||||
assert md[utils.X_OBJECT_TYPE] == utils.FILE
|
|
||||||
assert md[utils.X_CONTENT_TYPE] == utils.FILE_TYPE
|
|
||||||
assert md[utils.X_CONTENT_LENGTH] == os.path.getsize(tf.name)
|
|
||||||
assert md[utils.X_TIMESTAMP] == utils.normalize_timestamp(os.path.getctime(tf.name))
|
|
||||||
assert md[utils.X_ETAG] == utils._get_etag(tf.name)
|
|
||||||
|
|
||||||
def test_create_object_metadata_dir(self):
|
|
||||||
td = tempfile.mkdtemp()
|
|
||||||
try:
|
|
||||||
r_md = utils.create_object_metadata(td)
|
|
||||||
|
|
||||||
xkey = _xkey(td, utils.METADATA_KEY)
|
|
||||||
assert len(_xattrs.keys()) == 1
|
|
||||||
assert xkey in _xattrs
|
|
||||||
assert _xattr_op_cnt['set'] == 1
|
|
||||||
md = deserialize_metadata(_xattrs[xkey])
|
|
||||||
assert r_md == md
|
|
||||||
|
|
||||||
for key in self.obj_keys:
|
|
||||||
assert key in md, "Expected key %s in %r" % (key, md)
|
|
||||||
assert md[utils.X_TYPE] == utils.OBJECT
|
|
||||||
assert md[utils.X_OBJECT_TYPE] == utils.DIR_NON_OBJECT
|
|
||||||
assert md[utils.X_CONTENT_TYPE] == utils.DIR_TYPE
|
|
||||||
assert md[utils.X_CONTENT_LENGTH] == 0
|
|
||||||
assert md[utils.X_TIMESTAMP] == utils.normalize_timestamp(os.path.getctime(td))
|
|
||||||
assert md[utils.X_ETAG] == hashlib.md5().hexdigest()
|
|
||||||
finally:
|
|
||||||
os.rmdir(td)
|
|
||||||
|
|
||||||
def test_validate_object_empty(self):
|
|
||||||
ret = utils.validate_object({})
|
|
||||||
assert not ret
|
|
||||||
|
|
||||||
def test_validate_object_missing_keys(self):
|
|
||||||
ret = utils.validate_object({'foo': 'bar'})
|
|
||||||
assert not ret
|
|
||||||
|
|
||||||
def test_validate_object_bad_type(self):
|
|
||||||
md = {utils.X_TIMESTAMP: 'na',
|
|
||||||
utils.X_CONTENT_TYPE: 'na',
|
|
||||||
utils.X_ETAG: 'bad',
|
|
||||||
utils.X_CONTENT_LENGTH: 'na',
|
|
||||||
utils.X_TYPE: 'bad',
|
|
||||||
utils.X_OBJECT_TYPE: 'na'}
|
|
||||||
ret = utils.validate_object(md)
|
|
||||||
assert not ret
|
|
||||||
|
|
||||||
def test_validate_object_good_type(self):
|
|
||||||
md = {utils.X_TIMESTAMP: 'na',
|
|
||||||
utils.X_CONTENT_TYPE: 'na',
|
|
||||||
utils.X_ETAG: 'bad',
|
|
||||||
utils.X_CONTENT_LENGTH: 'na',
|
|
||||||
utils.X_TYPE: utils.OBJECT,
|
|
||||||
utils.X_OBJECT_TYPE: 'na'}
|
|
||||||
ret = utils.validate_object(md)
|
|
||||||
assert ret
|
|
||||||
|
|
||||||
def test_validate_object_with_stat(self):
|
|
||||||
md = {utils.X_TIMESTAMP: 'na',
|
|
||||||
utils.X_CONTENT_TYPE: 'na',
|
|
||||||
utils.X_ETAG: 'bad',
|
|
||||||
utils.X_CONTENT_LENGTH: '12345',
|
|
||||||
utils.X_TYPE: utils.OBJECT,
|
|
||||||
utils.X_OBJECT_TYPE: 'na'}
|
|
||||||
fake_stat = Mock(st_size=12346, st_mode=33188)
|
|
||||||
self.assertFalse(utils.validate_object(md, fake_stat))
|
|
||||||
fake_stat = Mock(st_size=12345, st_mode=33188)
|
|
||||||
self.assertTrue(utils.validate_object(md, fake_stat))
|
|
||||||
|
|
||||||
def test_write_pickle(self):
|
def test_write_pickle(self):
|
||||||
td = tempfile.mkdtemp()
|
td = tempfile.mkdtemp()
|
||||||
try:
|
try:
|
||||||
|
@ -188,14 +188,13 @@ class TestDiskFile(unittest.TestCase):
|
|||||||
fd.write("1234")
|
fd.write("1234")
|
||||||
stats = os.stat(the_file)
|
stats = os.stat(the_file)
|
||||||
ts = normalize_timestamp(stats.st_ctime)
|
ts = normalize_timestamp(stats.st_ctime)
|
||||||
etag = md5()
|
etag = md5("1234").hexdigest()
|
||||||
etag.update("1234")
|
|
||||||
etag = etag.hexdigest()
|
|
||||||
exp_md = {
|
exp_md = {
|
||||||
'Content-Length': 4,
|
'Content-Length': 4,
|
||||||
'ETag': etag,
|
'ETag': etag,
|
||||||
'X-Timestamp': ts,
|
'X-Timestamp': ts,
|
||||||
'X-Object-PUT-Mtime': normalize_timestamp(stats.st_mtime),
|
'X-Object-PUT-Mtime': normalize_timestamp(stats.st_mtime),
|
||||||
|
'X-Object-Sysmeta-Update-Container': True,
|
||||||
'Content-Type': 'application/octet-stream'}
|
'Content-Type': 'application/octet-stream'}
|
||||||
gdf = self._get_diskfile("vol0", "p57", "ufo47", "bar", "z")
|
gdf = self._get_diskfile("vol0", "p57", "ufo47", "bar", "z")
|
||||||
assert gdf._obj == "z"
|
assert gdf._obj == "z"
|
||||||
@ -227,10 +226,10 @@ class TestDiskFile(unittest.TestCase):
|
|||||||
ini_md = {
|
ini_md = {
|
||||||
'X-Type': 'Object',
|
'X-Type': 'Object',
|
||||||
'X-Object-Type': 'file',
|
'X-Object-Type': 'file',
|
||||||
'Content-Length': 4,
|
'Content-Length': os.path.getsize(the_file),
|
||||||
'ETag': 'etag',
|
'ETag': md5("1234").hexdigest(),
|
||||||
'X-Timestamp': 'ts',
|
'X-Timestamp': os.stat(the_file).st_mtime,
|
||||||
'Content-Type': 'application/loctet-stream'}
|
'Content-Type': 'application/octet-stream'}
|
||||||
_metadata[_mapit(the_file)] = ini_md
|
_metadata[_mapit(the_file)] = ini_md
|
||||||
exp_md = ini_md.copy()
|
exp_md = ini_md.copy()
|
||||||
del exp_md['X-Type']
|
del exp_md['X-Type']
|
||||||
@ -273,10 +272,10 @@ class TestDiskFile(unittest.TestCase):
|
|||||||
ini_md = {
|
ini_md = {
|
||||||
'X-Type': 'Object',
|
'X-Type': 'Object',
|
||||||
'X-Object-Type': 'dir',
|
'X-Object-Type': 'dir',
|
||||||
'Content-Length': 5,
|
'Content-Length': 0,
|
||||||
'ETag': 'etag',
|
'ETag': md5().hexdigest(),
|
||||||
'X-Timestamp': 'ts',
|
'X-Timestamp': os.stat(the_dir).st_mtime,
|
||||||
'Content-Type': 'application/loctet-stream'}
|
'Content-Type': 'application/directory'}
|
||||||
_metadata[_mapit(the_dir)] = ini_md
|
_metadata[_mapit(the_dir)] = ini_md
|
||||||
exp_md = ini_md.copy()
|
exp_md = ini_md.copy()
|
||||||
del exp_md['X-Type']
|
del exp_md['X-Type']
|
||||||
@ -493,7 +492,7 @@ class TestDiskFile(unittest.TestCase):
|
|||||||
'X-Object-Type': 'file',
|
'X-Object-Type': 'file',
|
||||||
'Content-Length': 4,
|
'Content-Length': 4,
|
||||||
'ETag': 'etag',
|
'ETag': 'etag',
|
||||||
'X-Timestamp': 'ts',
|
'X-Timestamp': '1234',
|
||||||
'Content-Type': 'application/loctet-stream'}
|
'Content-Type': 'application/loctet-stream'}
|
||||||
_metadata[_mapit(the_file)] = ini_md
|
_metadata[_mapit(the_file)] = ini_md
|
||||||
gdf = self._get_diskfile("vol0", "p57", "ufo47", "bar", "z")
|
gdf = self._get_diskfile("vol0", "p57", "ufo47", "bar", "z")
|
||||||
@ -522,7 +521,7 @@ class TestDiskFile(unittest.TestCase):
|
|||||||
'Content-Length': 4,
|
'Content-Length': 4,
|
||||||
'name': 'z',
|
'name': 'z',
|
||||||
'ETag': 'etag',
|
'ETag': 'etag',
|
||||||
'X-Timestamp': 'ts'}
|
'X-Timestamp': '1234'}
|
||||||
_metadata[_mapit(the_file)] = ini_md
|
_metadata[_mapit(the_file)] = ini_md
|
||||||
gdf = self._get_diskfile("vol0", "p57", "ufo47", "bar", "z")
|
gdf = self._get_diskfile("vol0", "p57", "ufo47", "bar", "z")
|
||||||
|
|
||||||
@ -548,7 +547,7 @@ class TestDiskFile(unittest.TestCase):
|
|||||||
'X-Type': 'Object',
|
'X-Type': 'Object',
|
||||||
'Content-Length': 0,
|
'Content-Length': 0,
|
||||||
'ETag': 'etag',
|
'ETag': 'etag',
|
||||||
'X-Timestamp': 'ts',
|
'X-Timestamp': '1234',
|
||||||
'X-Object-Meta-test':'test',
|
'X-Object-Meta-test':'test',
|
||||||
'Content-Type': 'application/directory'}
|
'Content-Type': 'application/directory'}
|
||||||
_metadata[_mapit(the_dir)] = init_md
|
_metadata[_mapit(the_dir)] = init_md
|
||||||
@ -571,7 +570,6 @@ class TestDiskFile(unittest.TestCase):
|
|||||||
DIR_OBJECT)
|
DIR_OBJECT)
|
||||||
self.assertFalse('X-Object-Meta-test' in _metadata[_mapit(the_dir)])
|
self.assertFalse('X-Object-Meta-test' in _metadata[_mapit(the_dir)])
|
||||||
|
|
||||||
|
|
||||||
def test_write_metadata_w_meta_file(self):
|
def test_write_metadata_w_meta_file(self):
|
||||||
the_path = os.path.join(self.td, "vol0", "ufo47", "bar")
|
the_path = os.path.join(self.td, "vol0", "ufo47", "bar")
|
||||||
the_file = os.path.join(the_path, "z")
|
the_file = os.path.join(the_path, "z")
|
||||||
@ -625,7 +623,7 @@ class TestDiskFile(unittest.TestCase):
|
|||||||
assert gdf._metadata is None
|
assert gdf._metadata is None
|
||||||
newmd = {
|
newmd = {
|
||||||
'ETag': 'etag',
|
'ETag': 'etag',
|
||||||
'X-Timestamp': 'ts',
|
'X-Timestamp': '1234',
|
||||||
'Content-Type': 'application/directory'}
|
'Content-Type': 'application/directory'}
|
||||||
with gdf.create(None, None) as dw:
|
with gdf.create(None, None) as dw:
|
||||||
dw.put(newmd)
|
dw.put(newmd)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user