change ite logic and add more tests

This commit is contained in:
David Goetz 2011-03-16 07:55:07 -07:00
parent 61e53372be
commit 5482035bc0
7 changed files with 107 additions and 73 deletions

View File

@ -287,6 +287,40 @@ def direct_put_object(node, part, account, container, name, contents,
return resp.getheader('etag').strip('"') return resp.getheader('etag').strip('"')
def direct_post_object(node, part, account, container, name, headers,
conn_timeout=5, response_timeout=15):
"""
Put object directly from the object server.
:param node: node dictionary from the ring
:param part: partition the container is on
:param account: account name
:param container: container name
:param name: object name
:param headers: headers to store as metadata
:param conn_timeout: timeout in seconds for establishing the connection
:param response_timeout: timeout in seconds for getting the response
:raises ClientException: HTTP POST request failed
"""
path = '/%s/%s/%s' % (account, container, name)
headers['X-Timestamp'] = normalize_timestamp(time())
with Timeout(conn_timeout):
conn = http_connect(node['ip'], node['port'], node['device'], part,
'POST', path, headers=headers)
with Timeout(response_timeout):
resp = conn.getresponse()
resp.read()
if resp.status < 200 or resp.status >= 300:
raise ClientException(
'Object server %s:%s direct POST %s gave status %s' %
(node['ip'], node['port'],
repr('/%s/%s%s' % (node['device'], part, path)),
resp.status),
http_host=node['ip'], http_port=node['port'],
http_device=node['device'], http_status=resp.status,
http_reason=resp.reason)
def direct_delete_object(node, part, account, container, obj, def direct_delete_object(node, part, account, container, obj,
conn_timeout=5, response_timeout=15, headers={}): conn_timeout=5, response_timeout=15, headers={}):
""" """

View File

@ -155,7 +155,6 @@ class AuditorWorker(object):
os.path.join(self.devices, device), path) os.path.join(self.devices, device), path)
return return
except Exception: except Exception:
#raise
self.errors += 1 self.errors += 1
self.logger.exception(_('ERROR Trying to audit %s'), path) self.logger.exception(_('ERROR Trying to audit %s'), path)
return return

View File

@ -132,7 +132,6 @@ class DiskFile(object):
def __init__(self, path, device, partition, account, container, obj, def __init__(self, path, device, partition, account, container, obj,
logger, keep_data_fp=False, disk_chunk_size=65536): logger, keep_data_fp=False, disk_chunk_size=65536):
self.disk_chunk_size = disk_chunk_size self.disk_chunk_size = disk_chunk_size
self.name = '/' + '/'.join((account, container, obj)) self.name = '/' + '/'.join((account, container, obj))
name_hash = hash_path(account, container, obj) name_hash = hash_path(account, container, obj)
@ -146,14 +145,13 @@ class DiskFile(object):
self.data_file = None self.data_file = None
self.fp = None self.fp = None
self.iter_etag = None self.iter_etag = None
self.last_iter_pos = 0 self.started_at_0 = False
self.read_to_eof = False self.read_to_eof = False
self.quarantined_dir = None self.quarantined_dir = None
self.keep_cache = False self.keep_cache = False
if not os.path.exists(self.datadir): if not os.path.exists(self.datadir):
return return
files = sorted(os.listdir(self.datadir), reverse=True) files = sorted(os.listdir(self.datadir), reverse=True)
for file in files: for file in files:
if file.endswith('.ts'): if file.endswith('.ts'):
self.data_file = self.meta_file = None self.data_file = self.meta_file = None
@ -183,18 +181,14 @@ class DiskFile(object):
try: try:
dropped_cache = 0 dropped_cache = 0
read = 0 read = 0
self.last_iter_pos = 0 if self.fp.tell() == 0:
self.iter_etag = md5() self.started_at_0 = True
self.iter_etag = md5()
while True: while True:
pre_read_pos = self.fp.tell()
chunk = self.fp.read(self.disk_chunk_size) chunk = self.fp.read(self.disk_chunk_size)
if chunk: if chunk:
if self.iter_etag and self.last_iter_pos == pre_read_pos: if self.iter_etag:
self.iter_etag.update(chunk) self.iter_etag.update(chunk)
self.last_iter_pos += len(chunk)
else:
# file has not been read sequentially
self.iter_etag = None
read += len(chunk) read += len(chunk)
if read - dropped_cache > (1024 * 1024): if read - dropped_cache > (1024 * 1024):
self.drop_cache(self.fp.fileno(), dropped_cache, self.drop_cache(self.fp.fileno(), dropped_cache,
@ -228,7 +222,6 @@ class DiskFile(object):
def _handle_close_quarantine(self): def _handle_close_quarantine(self):
"""Check if file needs to be quarantined""" """Check if file needs to be quarantined"""
obj_size = None
try: try:
obj_size = self.get_data_file_size() obj_size = self.get_data_file_size()
except DiskFileError, e: except DiskFileError, e:
@ -237,17 +230,17 @@ class DiskFile(object):
except DiskFileNotExist: except DiskFileNotExist:
return return
if (self.iter_etag and self.read_to_eof and self.metadata.get('ETag') if (self.iter_etag and self.started_at_0 and self.read_to_eof and
and obj_size == self.last_iter_pos and self.metadata.has_key('ETag') and
self.iter_etag.hexdigest() != self.metadata['ETag']): self.iter_etag.hexdigest() != self.metadata.get('ETag')):
self.quarantine() self.quarantine()
def close(self, verify_file=True): def close(self, verify_file=True):
""" """
Close the file. Close the file. Will handle quarantining file if necessary.
:param verify_file: Defaults to True, will handle quarantining :param verify_file: Defaults to True. If false, will not check
file if necessary. file to see if it needs quarantining.
""" """
if self.fp: if self.fp:
try: try:
@ -463,13 +456,11 @@ class ObjectController(object):
response_class = HTTPNotFound response_class = HTTPNotFound
else: else:
response_class = HTTPAccepted response_class = HTTPAccepted
try: try:
file_size = file.get_data_file_size() file_size = file.get_data_file_size()
except (DiskFileError, DiskFileNotExist): except (DiskFileError, DiskFileNotExist):
file.quarantine() file.quarantine()
return HTTPNotFound(request=request) return HTTPNotFound(request=request)
metadata = {'X-Timestamp': request.headers['x-timestamp']} metadata = {'X-Timestamp': request.headers['x-timestamp']}
metadata.update(val for val in request.headers.iteritems() metadata.update(val for val in request.headers.iteritems()
if val[0].lower().startswith('x-object-meta-')) if val[0].lower().startswith('x-object-meta-'))

View File

@ -42,13 +42,11 @@ class TestObjectFailures(unittest.TestCase):
for file in files: for file in files:
return os.path.join(obj_dir, file) return os.path.join(obj_dir, file)
def run_quarantine(self): def _setup_data_file(self, container, obj, data):
container = 'container-%s' % uuid4()
obj = 'object-%s' % uuid4()
client.put_container(self.url, self.token, container) client.put_container(self.url, self.token, container)
client.put_object(self.url, self.token, container, obj, 'VERIFY') client.put_object(self.url, self.token, container, obj, data)
odata = client.get_object(self.url, self.token, container, obj)[-1] odata = client.get_object(self.url, self.token, container, obj)[-1]
self.assertEquals(odata, 'VERIFY') self.assertEquals(odata, data)
opart, onodes = self.object_ring.get_nodes( opart, onodes = self.object_ring.get_nodes(
self.account, container, obj) self.account, container, obj)
onode = onodes[0] onode = onodes[0]
@ -62,7 +60,14 @@ class TestObjectFailures(unittest.TestCase):
device, opart, device, opart,
hash_str[-3:], hash_str) hash_str[-3:], hash_str)
data_file = self._get_data_file_path(obj_dir) data_file = self._get_data_file_path(obj_dir)
return onode, opart, data_file
def run_quarantine(self):
container = 'container-%s' % uuid4()
obj = 'object-%s' % uuid4()
onode, opart, data_file = self._setup_data_file(container, obj,
'VERIFY')
with open(data_file) as fp: with open(data_file) as fp:
metadata = read_metadata(fp) metadata = read_metadata(fp)
metadata['ETag'] = 'badetag' metadata['ETag'] = 'badetag'
@ -82,25 +87,8 @@ class TestObjectFailures(unittest.TestCase):
def run_quarantine_range_etag(self): def run_quarantine_range_etag(self):
container = 'container-range-%s' % uuid4() container = 'container-range-%s' % uuid4()
obj = 'object-range-%s' % uuid4() obj = 'object-range-%s' % uuid4()
client.put_container(self.url, self.token, container) onode, opart, data_file = self._setup_data_file(container, obj,
client.put_object(self.url, self.token, container, obj, 'RANGE') 'RANGE')
odata = client.get_object(self.url, self.token, container, obj)[-1]
self.assertEquals(odata, 'RANGE')
opart, onodes = self.object_ring.get_nodes(
self.account, container, obj)
onode = onodes[0]
node_id = (onode['port'] - 6000) / 10
device = onode['device']
hash_str = hash_path(self.account, container, obj)
obj_server_conf = readconf('/etc/swift/object-server/%s.conf' %
node_id)
devices = obj_server_conf['app:object-server']['devices']
obj_dir = '%s/%s/objects/%s/%s/%s/' % (devices,
device, opart,
hash_str[-3:], hash_str)
data_file = self._get_data_file_path(obj_dir)
with open(data_file) as fp: with open(data_file) as fp:
metadata = read_metadata(fp) metadata = read_metadata(fp)
metadata['ETag'] = 'badetag' metadata['ETag'] = 'badetag'
@ -122,32 +110,10 @@ class TestObjectFailures(unittest.TestCase):
except client.ClientException, e: except client.ClientException, e:
self.assertEquals(e.http_status, 404) self.assertEquals(e.http_status, 404)
def run_quarantine_zero_byte_get(self):
def run_quarantine_range_zero_byte(self):
container = 'container-zbyte-%s' % uuid4() container = 'container-zbyte-%s' % uuid4()
obj = 'object-zbyte-%s' % uuid4() obj = 'object-zbyte-%s' % uuid4()
client.put_container(self.url, self.token, container) onode, opart, data_file = self._setup_data_file(container, obj, 'DATA')
client.put_object(self.url, self.token, container, obj, 'ZBYTE')
odata = client.get_object(self.url, self.token, container, obj)[-1]
self.assertEquals(odata, 'ZBYTE')
opart, onodes = self.object_ring.get_nodes(
self.account, container, obj)
onode = onodes[0]
node_id = (onode['port'] - 6000) / 10
device = onode['device']
hash_str = hash_path(self.account, container, obj)
obj_server_conf = readconf('/etc/swift/object-server/%s.conf' %
node_id)
devices = obj_server_conf['app:object-server']['devices']
obj_dir = '%s/%s/objects/%s/%s/%s/' % (devices,
device, opart,
hash_str[-3:], hash_str)
data_file = self._get_data_file_path(obj_dir)
with open(data_file) as fp: with open(data_file) as fp:
metadata = read_metadata(fp) metadata = read_metadata(fp)
os.unlink(data_file) os.unlink(data_file)
@ -163,11 +129,52 @@ class TestObjectFailures(unittest.TestCase):
except client.ClientException, e: except client.ClientException, e:
self.assertEquals(e.http_status, 404) self.assertEquals(e.http_status, 404)
def run_quarantine_zero_byte_head(self):
container = 'container-zbyte-%s' % uuid4()
obj = 'object-zbyte-%s' % uuid4()
onode, opart, data_file = self._setup_data_file(container, obj, 'DATA')
with open(data_file) as fp:
metadata = read_metadata(fp)
os.unlink(data_file)
with open(data_file,'w') as fp:
write_metadata(fp, metadata)
try:
resp = direct_client.direct_head_object(onode, opart, self.account,
container, obj,
conn_timeout=1,
response_timeout=1)
raise "Did not quarantine object"
except client.ClientException, e:
self.assertEquals(e.http_status, 404)
def run_quarantine_zero_byte_post(self):
container = 'container-zbyte-%s' % uuid4()
obj = 'object-zbyte-%s' % uuid4()
onode, opart, data_file = self._setup_data_file(container, obj, 'DATA')
with open(data_file) as fp:
metadata = read_metadata(fp)
os.unlink(data_file)
with open(data_file,'w') as fp:
write_metadata(fp, metadata)
try:
resp = direct_client.direct_post_object(
onode, opart, self.account,
container, obj,
{'X-Object-Meta-1': 'One', 'X-Object-Meta-Two': 'Two'},
conn_timeout=1,
response_timeout=1)
raise "Did not quarantine object"
except client.ClientException, e:
self.assertEquals(e.http_status, 404)
def test_runner(self): def test_runner(self):
self.run_quarantine() self.run_quarantine()
self.run_quarantine_range_etag() self.run_quarantine_range_etag()
self.run_quarantine_range_zero_byte() self.run_quarantine_zero_byte_get()
self.run_quarantine_zero_byte_head()
self.run_quarantine_zero_byte_post()
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -104,7 +104,7 @@ class TestAuditor(unittest.TestCase):
} }
self.disk_file.put(fd, tmppath, metadata) self.disk_file.put(fd, tmppath, metadata)
pre_quarantines = self.auditor.quarantines pre_quarantines = self.auditor.quarantines
# remake to it will have metadata # remake so it will have metadata
self.disk_file = DiskFile(self.devices, 'sda', '0', 'a', 'c', 'o', self.disk_file = DiskFile(self.devices, 'sda', '0', 'a', 'c', 'o',
self.logger) self.logger)

View File

@ -25,7 +25,6 @@ import fcntl
import time import time
import tempfile import tempfile
from contextlib import contextmanager from contextlib import contextmanager
from eventlet import tpool
from eventlet.green import subprocess from eventlet.green import subprocess
from test.unit import FakeLogger from test.unit import FakeLogger
from swift.common import utils from swift.common import utils

View File

@ -37,7 +37,7 @@ from swift.obj import server as object_server
from swift.common.utils import hash_path, mkdirs, normalize_timestamp, \ from swift.common.utils import hash_path, mkdirs, normalize_timestamp, \
NullLogger, storage_directory NullLogger, storage_directory
from swift.common.exceptions import DiskFileNotExist from swift.common.exceptions import DiskFileNotExist
from eventlet import tpool
class TestDiskFile(unittest.TestCase): class TestDiskFile(unittest.TestCase):
"""Test swift.obj.server.DiskFile""" """Test swift.obj.server.DiskFile"""
@ -47,6 +47,10 @@ class TestDiskFile(unittest.TestCase):
self.testdir = os.path.join(mkdtemp(), 'tmp_test_obj_server_DiskFile') self.testdir = os.path.join(mkdtemp(), 'tmp_test_obj_server_DiskFile')
mkdirs(os.path.join(self.testdir, 'sda1', 'tmp')) mkdirs(os.path.join(self.testdir, 'sda1', 'tmp'))
def fake_exe(*args, **kwargs):
pass
tpool.execute = fake_exe
def tearDown(self): def tearDown(self):
""" Tear down for testing swift.object_server.ObjectController """ """ Tear down for testing swift.object_server.ObjectController """
rmtree(os.path.dirname(self.testdir)) rmtree(os.path.dirname(self.testdir))