swift-ceph-backend/tests/test_rados_diskfile.py
Thiago da Silva d90d1ca900 open and close ioctx only once
The io context handle should be open only once in the life
of the application, there's no need to open and close
with every I/O transaction

Change-Id: I9948a71dbdcd20b4732c109ad35a400bd5962766
Signed-off-by: Thiago da Silva <thiago@redhat.com>
2015-09-16 19:32:54 +00:00

459 lines
16 KiB
Python

# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (C) 2013 eNovance SAS <licensing@enovance.com>
#
# 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.
import mock
from mock import call, patch
import cPickle as pickle
import unittest
from hashlib import md5
from swift.common.exceptions import DiskFileQuarantined, \
DiskFileCollision, DiskFileNotOpen
from swift_ceph_backend.rados_diskfile import RadosFileSystem
from swift_ceph_backend.rados_diskfile import METADATA_KEY
class TestRadosDiskFile(unittest.TestCase):
def setUp(self):
super(TestRadosDiskFile, self).setUp()
self.mock_rados = mock.MagicMock(name='rados')
self.Rados = self.mock_rados.Rados.return_value
self.ioctx = self.Rados.open_ioctx.return_value
self.rados_ceph_conf = 'xxx-ceph.conf'
self.rados_name = 'xxx-rados-name'
self.rados_pool = 'xxx-rados-pool'
self.device = 'device'
self.partition = '0'
self.account = 'account'
self.container = 'container'
self.obj_name = 'myobject'
self.logger = mock.Mock()
self.rdf = RadosFileSystem(self.rados_ceph_conf,
self.rados_name,
self.rados_pool,
self.logger,
rados=self.mock_rados)
self.df = self.rdf.get_diskfile(self.device,
self.partition,
self.account,
self.container,
self.obj_name)
def tearDown(self):
super(TestRadosDiskFile, self).tearDown()
self.mock_rados.reset_mock()
self.Rados.reset_mock()
self.ioctx.reset_mock()
del self.rdf
del self.df
def _obj_name(self):
return '/' + '/'.join((self.device, self.partition,
self.account, self.container, self.obj_name))
def _assert_if_rados_not_opened(self):
self.mock_rados.Rados.assert_called_once_with(
conffile=self.rados_ceph_conf, rados_id=self.rados_name)
self.Rados.connect.assert_called_once_with()
self.Rados.open_ioctx.assert_called_once_with(self.rados_pool)
def _assert_if_rados_opened(self):
assert((self.mock_rados.Rados.call_count == 0) and
(self.Rados.connect.call_count == 0) and
(self.Rados.open_ioctx.call_count == 0))
def _assert_if_rados_closed(self):
assert((self.ioctx.close.call_count == 0) and
(self.Rados.shutdown.call_count == 0))
def _assert_if_rados_opened_closed(self):
assert((self.mock_rados.Rados.call_count > 0) and
(self.Rados.connect.call_count > 0) and
(self.Rados.open_ioctx.call_count > 0))
assert((self.Rados.connect.call_count > 0) and
(self.Rados.open_ioctx.call_count ==
self.Rados.connect.call_count))
def test_df_open_1(self):
meta = {'name': self._obj_name(), 'Content-Length': 0}
self.ioctx.get_xattr.return_value = pickle.dumps(meta)
self.ioctx.stat.return_value = (0, 0)
with self.df.open():
pass
self._assert_if_rados_not_opened()
self.ioctx.get_xattr.assert_called_once_with(self._obj_name(),
METADATA_KEY)
def test_df_open_invalid_name(self):
meta = {'name': 'invalid', 'Content-Length': 0}
self.ioctx.get_xattr.return_value = pickle.dumps(meta)
self.ioctx.stat.return_value = (0, 0)
success = False
try:
self.df.open()
except DiskFileCollision:
success = True
except Exception:
pass
finally:
assert(success)
def test_df_open_invalid_content_length(self):
meta = {'name': self._obj_name(), 'Content-Length': 100}
self.ioctx.get_xattr.return_value = pickle.dumps(meta)
self.ioctx.stat.return_value = (0, 0)
success = False
try:
self.df.open()
except DiskFileQuarantined:
success = True
except Exception:
pass
finally:
assert(success)
def test_df_notopen_check(self):
success = False
try:
with self.df:
pass
except DiskFileNotOpen:
success = True
except Exception:
pass
finally:
assert(success)
self._assert_if_rados_opened()
self._assert_if_rados_closed()
def test_df_notopen_get_metadata(self):
success = False
try:
self.df.get_metadata()
except DiskFileNotOpen:
success = True
except Exception:
pass
finally:
assert(success)
self._assert_if_rados_opened()
self._assert_if_rados_closed()
def test_df_get_metadata(self):
meta = {'name': self._obj_name(), 'Content-Length': 0}
self.ioctx.get_xattr.return_value = pickle.dumps(meta)
self.ioctx.stat.return_value = (0, 0)
success = False
ret_meta = None
try:
with self.df.open():
ret_meta = self.df.get_metadata()
success = True
except Exception:
pass
finally:
assert(success)
assert(ret_meta == meta)
self._assert_if_rados_not_opened()
def test_df_read_metadata(self):
meta = {'name': self._obj_name(), 'Content-Length': 0}
self.ioctx.get_xattr.return_value = pickle.dumps(meta)
self.ioctx.stat.return_value = (0, 0)
success = False
ret_meta = None
try:
ret_meta = self.df.read_metadata()
success = True
except Exception:
pass
finally:
assert(success)
assert(ret_meta == meta)
self._assert_if_rados_not_opened()
def test_df_notopen_reader(self):
success = False
try:
self.df.reader()
except DiskFileNotOpen:
success = True
except Exception:
pass
finally:
assert(success)
self._assert_if_rados_opened()
self._assert_if_rados_closed()
def test_df_open_reader_1(self):
meta = {'name': self._obj_name(), 'Content-Length': 0}
self.ioctx.get_xattr.return_value = pickle.dumps(meta)
self.ioctx.stat.return_value = (0, 0)
success = False
try:
with self.df.open():
self.df.reader()
except KeyError:
success = True
pass
finally:
assert(success)
self._assert_if_rados_not_opened()
def test_df_open_reader_2(self):
meta = {'name': self._obj_name(), 'Content-Length': 0, 'ETag': ''}
self.ioctx.get_xattr.return_value = pickle.dumps(meta)
self.ioctx.stat.return_value = (0, 0)
success = False
try:
with self.df.open():
rdr = self.df.reader()
rdr.close()
success = True
except Exception:
pass
finally:
assert(success)
self._assert_if_rados_opened_closed()
def test_df_reader_iter_invalid_cont_len(self):
etag = md5()
fcont = '123456789'
etag.update(fcont)
meta = {'name': self._obj_name(), 'Content-Length': len(fcont),
'ETag': etag.hexdigest()}
self.ioctx.get_xattr.return_value = pickle.dumps(meta)
self.ioctx.stat.return_value = (len(fcont), 0)
success = False
try:
with self.df.open():
rdr = self.df.reader()
num_chunks = 0
self.ioctx.read.return_value = fcont
for chunk in rdr:
num_chunks += 1
assert(chunk == fcont)
if num_chunks == 3:
self.ioctx.read.return_value = None
assert(num_chunks == 3)
success = True
except Exception:
pass
finally:
assert(success)
self._assert_if_rados_opened_closed()
# check read calls
call_list = [call.read(self._obj_name(), offset=0),
call.read(self._obj_name(), offset=len(fcont)),
call.read(self._obj_name(), offset=(2 * len(fcont))),
call.read(self._obj_name(), offset=(3 * len(fcont)))]
self.ioctx.assert_has_calls(call_list)
self.ioctx.remove_object.assert_called_once_with(self._obj_name())
def test_df_reader_iter_invalid_etag(self):
etag = md5()
fcont = '123456789'
etag.update(fcont)
meta = {'name': self._obj_name(), 'Content-Length': (3 * len(fcont)),
'ETag': etag.hexdigest()}
self.ioctx.get_xattr.return_value = pickle.dumps(meta)
self.ioctx.stat.return_value = ((len(fcont) * 3), 0)
success = False
try:
with self.df.open():
rdr = self.df.reader()
num_chunks = 0
self.ioctx.read.return_value = fcont
for chunk in rdr:
num_chunks += 1
assert(chunk == fcont)
if num_chunks == 3:
self.ioctx.read.return_value = None
assert(num_chunks == 3)
success = True
except Exception:
pass
finally:
assert(success)
self._assert_if_rados_opened_closed()
# check read calls
call_list = [call.read(self._obj_name(), offset=0),
call.read(self._obj_name(), offset=len(fcont)),
call.read(self._obj_name(), offset=(2 * len(fcont))),
call.read(self._obj_name(), offset=(3 * len(fcont)))]
self.ioctx.assert_has_calls(call_list)
self.ioctx.remove_object.assert_called_once_with(self._obj_name())
def test_df_reader_iter_all_ok(self):
etag = md5()
fcont = '123456789'
etag.update(fcont)
etag.update(fcont)
etag.update(fcont)
meta = {'name': self._obj_name(), 'Content-Length': (3 * len(fcont)),
'ETag': etag.hexdigest()}
self.ioctx.get_xattr.return_value = pickle.dumps(meta)
self.ioctx.stat.return_value = ((3 * len(fcont)), 0)
success = False
try:
with self.df.open():
rdr = self.df.reader()
num_chunks = 0
self.ioctx.read.return_value = fcont
for chunk in rdr:
num_chunks += 1
assert(chunk == fcont)
if num_chunks == 3:
self.ioctx.read.return_value = None
assert(num_chunks == 3)
success = True
except Exception:
pass
finally:
assert(success)
self._assert_if_rados_opened_closed()
# check read calls
call_list = [call.read(self._obj_name(), offset=0),
call.read(self._obj_name(), offset=len(fcont)),
call.read(self._obj_name(), offset=(2 * len(fcont))),
call.read(self._obj_name(), offset=(3 * len(fcont)))]
self.ioctx.assert_has_calls(call_list)
# if everything is perfect, the object will not be deleted
assert(self.ioctx.remove_object.call_count == 0)
def test_df_reader_iter_range(self):
etag = md5()
fcont = '0123456789'
etag.update(fcont)
meta = {'name': self._obj_name(), 'Content-Length': len(fcont),
'ETag': etag.hexdigest()}
self.ioctx.get_xattr.return_value = pickle.dumps(meta)
self.ioctx.stat.return_value = (len(fcont), 0)
success = False
try:
with self.df.open():
rdr = self.df.reader()
num_chunks = 0
def ioctx_read(obj_name, length=8192, offset=0):
assert(obj_name == self._obj_name())
return fcont[offset:]
self.ioctx.read = ioctx_read
for chunk in rdr.app_iter_range(1, 8):
num_chunks += 1
assert(chunk == '1234567')
assert(num_chunks == 1)
success = True
except Exception:
pass
finally:
assert(success)
self._assert_if_rados_opened_closed()
assert(self.ioctx.remove_object.call_count == 0)
def test_df_writer_1(self):
with self.df.create():
pass
assert(self.ioctx.trunc.call_count == 0)
self._assert_if_rados_not_opened()
with self.df.create(500):
pass
self.ioctx.trunc.assert_called_once_with(self._obj_name(), 500)
def test_df_writer_write(self):
fcont = '0123456789'
writes = []
def mock_write(self, obj, offset, data):
writes.append((data, offset))
return 2
with patch('swift_ceph_backend.rados_diskfile.'
'RadosFileSystem._radosfs.write', mock_write):
with self.df.create() as writer:
assert(writer.write(fcont) == len(fcont))
check_list = [
(fcont, 0),
(fcont[2:], 2),
(fcont[4:], 4),
(fcont[6:], 6),
(fcont[8:], 8)]
assert(writes == check_list)
self._assert_if_rados_not_opened()
def test_df_writer_put(self):
meta = {'Content-Length': 0,
'ETag': ''}
with self.df.create() as writer:
writer.put(meta)
old_metadata = pickle.dumps(meta)
ca = self.ioctx.set_xattr.call_args
check_1 = call(self._obj_name(), METADATA_KEY, old_metadata)
assert(ca == check_1)
assert(meta['name'] == self._obj_name())
self._assert_if_rados_not_opened()
def test_df_write_metadata(self):
meta = {'Content-Length': 0,
'ETag': ''}
self.df.write_metadata(meta)
old_metadata = pickle.dumps(meta)
ca = self.ioctx.set_xattr.call_args
check_1 = call(self._obj_name(), METADATA_KEY, old_metadata)
assert(ca == check_1)
assert(meta['name'] == self._obj_name())
self._assert_if_rados_not_opened()
def test_df_delete(self):
meta = {'name': self._obj_name(), 'Content-Length': 0,
'X-Timestamp': 0}
self.ioctx.get_xattr.return_value = pickle.dumps(meta)
self.ioctx.stat.return_value = (0, 0)
success = False
try:
self.df.delete(1)
success = True
except Exception:
pass
finally:
assert(success)
self._assert_if_rados_not_opened()
self.ioctx.remove_object.assert_called_once_with(self._obj_name())
if __name__ == '__main__':
unittest.main()