Merge "Provides proper error handling on builder unpickle"
This commit is contained in:
commit
b9f08a2be8
@ -830,10 +830,18 @@ def main(arguments=None):
|
|||||||
|
|
||||||
builder_file, ring_file = parse_builder_ring_filename_args(argv)
|
builder_file, ring_file = parse_builder_ring_filename_args(argv)
|
||||||
|
|
||||||
if exists(builder_file):
|
try:
|
||||||
builder = RingBuilder.load(builder_file)
|
builder = RingBuilder.load(builder_file)
|
||||||
elif len(argv) < 3 or argv[2] not in('create', 'write_builder'):
|
except exceptions.UnPicklingError as e:
|
||||||
print 'Ring Builder file does not exist: %s' % argv[1]
|
print e
|
||||||
|
exit(EXIT_ERROR)
|
||||||
|
except (exceptions.FileNotFoundError, exceptions.PermissionError) as e:
|
||||||
|
if len(argv) < 3 or argv[2] not in('create', 'write_builder'):
|
||||||
|
print e
|
||||||
|
exit(EXIT_ERROR)
|
||||||
|
except Exception as e:
|
||||||
|
print 'Problem occurred while reading builder file: %s. %s' % (
|
||||||
|
argv[1], e.message)
|
||||||
exit(EXIT_ERROR)
|
exit(EXIT_ERROR)
|
||||||
|
|
||||||
backup_dir = pathjoin(dirname(argv[1]), 'backups')
|
backup_dir = pathjoin(dirname(argv[1]), 'backups')
|
||||||
|
@ -123,6 +123,18 @@ class DuplicateDeviceError(RingBuilderError):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class UnPicklingError(SwiftException):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class FileNotFoundError(SwiftException):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class PermissionError(SwiftException):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ListingIterError(SwiftException):
|
class ListingIterError(SwiftException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
import bisect
|
import bisect
|
||||||
import copy
|
import copy
|
||||||
|
import errno
|
||||||
import itertools
|
import itertools
|
||||||
import math
|
import math
|
||||||
import random
|
import random
|
||||||
@ -1085,7 +1086,26 @@ class RingBuilder(object):
|
|||||||
:param builder_file: path to builder file to load
|
:param builder_file: path to builder file to load
|
||||||
:return: RingBuilder instance
|
:return: RingBuilder instance
|
||||||
"""
|
"""
|
||||||
builder = pickle.load(open(builder_file, 'rb'))
|
try:
|
||||||
|
fp = open(builder_file, 'rb')
|
||||||
|
except IOError as e:
|
||||||
|
if e.errno == errno.ENOENT:
|
||||||
|
raise exceptions.FileNotFoundError(
|
||||||
|
'Ring Builder file does not exist: %s' % builder_file)
|
||||||
|
elif e.errno in [errno.EPERM, errno.EACCES]:
|
||||||
|
raise exceptions.PermissionError(
|
||||||
|
'Ring Builder file cannot be accessed: %s' % builder_file)
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
with fp:
|
||||||
|
try:
|
||||||
|
builder = pickle.load(fp)
|
||||||
|
except Exception:
|
||||||
|
# raise error during unpickling as UnPicklingError
|
||||||
|
raise exceptions.UnPicklingError(
|
||||||
|
'Ring Builder file is invalid: %s' % builder_file)
|
||||||
|
|
||||||
if not hasattr(builder, 'devs'):
|
if not hasattr(builder, 'devs'):
|
||||||
builder_dict = builder
|
builder_dict = builder
|
||||||
builder = RingBuilder(1, 1, 1)
|
builder = RingBuilder(1, 1, 1)
|
||||||
|
@ -13,11 +13,14 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
import mock
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
|
import uuid
|
||||||
|
|
||||||
import swift.cli.ringbuilder
|
import swift.cli.ringbuilder
|
||||||
|
from swift.common import exceptions
|
||||||
from swift.common.ring import RingBuilder
|
from swift.common.ring import RingBuilder
|
||||||
|
|
||||||
|
|
||||||
@ -199,6 +202,65 @@ class TestCommands(unittest.TestCase):
|
|||||||
ring = RingBuilder.load(self.tmpfile)
|
ring = RingBuilder.load(self.tmpfile)
|
||||||
self.assertEqual(ring.replicas, 3.14159265359)
|
self.assertEqual(ring.replicas, 3.14159265359)
|
||||||
|
|
||||||
|
def test_validate(self):
|
||||||
|
self.create_sample_ring()
|
||||||
|
ring = RingBuilder.load(self.tmpfile)
|
||||||
|
ring.rebalance()
|
||||||
|
ring.save(self.tmpfile)
|
||||||
|
argv = ["", self.tmpfile, "validate"]
|
||||||
|
self.assertRaises(SystemExit, swift.cli.ringbuilder.main, argv)
|
||||||
|
|
||||||
|
def test_validate_empty_file(self):
|
||||||
|
open(self.tmpfile, 'a').close
|
||||||
|
argv = ["", self.tmpfile, "validate"]
|
||||||
|
try:
|
||||||
|
swift.cli.ringbuilder.main(argv)
|
||||||
|
except SystemExit as e:
|
||||||
|
self.assertEquals(e.code, 2)
|
||||||
|
|
||||||
|
def test_validate_corrupted_file(self):
|
||||||
|
self.create_sample_ring()
|
||||||
|
ring = RingBuilder.load(self.tmpfile)
|
||||||
|
ring.rebalance()
|
||||||
|
self.assertTrue(ring.validate()) # ring is valid until now
|
||||||
|
ring.save(self.tmpfile)
|
||||||
|
argv = ["", self.tmpfile, "validate"]
|
||||||
|
|
||||||
|
# corrupt the file
|
||||||
|
with open(self.tmpfile, 'wb') as f:
|
||||||
|
f.write(os.urandom(1024))
|
||||||
|
try:
|
||||||
|
swift.cli.ringbuilder.main(argv)
|
||||||
|
except SystemExit as e:
|
||||||
|
self.assertEquals(e.code, 2)
|
||||||
|
|
||||||
|
def test_validate_non_existent_file(self):
|
||||||
|
rand_file = '%s/%s' % ('/tmp', str(uuid.uuid4()))
|
||||||
|
argv = ["", rand_file, "validate"]
|
||||||
|
try:
|
||||||
|
swift.cli.ringbuilder.main(argv)
|
||||||
|
except SystemExit as e:
|
||||||
|
self.assertEquals(e.code, 2)
|
||||||
|
|
||||||
|
def test_validate_non_accessible_file(self):
|
||||||
|
with mock.patch.object(
|
||||||
|
RingBuilder, 'load',
|
||||||
|
mock.Mock(side_effect=exceptions.PermissionError)):
|
||||||
|
argv = ["", self.tmpfile, "validate"]
|
||||||
|
try:
|
||||||
|
swift.cli.ringbuilder.main(argv)
|
||||||
|
except SystemExit as e:
|
||||||
|
self.assertEquals(e.code, 2)
|
||||||
|
|
||||||
|
def test_validate_generic_error(self):
|
||||||
|
with mock.patch.object(RingBuilder, 'load',
|
||||||
|
mock.Mock(side_effect=
|
||||||
|
IOError('Generic error occurred'))):
|
||||||
|
argv = ["", self.tmpfile, "validate"]
|
||||||
|
try:
|
||||||
|
swift.cli.ringbuilder.main(argv)
|
||||||
|
except SystemExit as e:
|
||||||
|
self.assertEquals(e.code, 2)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
import errno
|
||||||
import mock
|
import mock
|
||||||
import operator
|
import operator
|
||||||
import os
|
import os
|
||||||
@ -913,17 +914,25 @@ class TestRingBuilder(unittest.TestCase):
|
|||||||
rb.rebalance()
|
rb.rebalance()
|
||||||
|
|
||||||
real_pickle = pickle.load
|
real_pickle = pickle.load
|
||||||
|
fake_open = mock.mock_open()
|
||||||
|
|
||||||
|
io_error_not_found = IOError()
|
||||||
|
io_error_not_found.errno = errno.ENOENT
|
||||||
|
|
||||||
|
io_error_no_perm = IOError()
|
||||||
|
io_error_no_perm.errno = errno.EPERM
|
||||||
|
|
||||||
|
io_error_generic = IOError()
|
||||||
|
io_error_generic.errno = errno.EOPNOTSUPP
|
||||||
try:
|
try:
|
||||||
#test a legit builder
|
#test a legit builder
|
||||||
fake_pickle = mock.Mock(return_value=rb)
|
fake_pickle = mock.Mock(return_value=rb)
|
||||||
fake_open = mock.Mock(return_value=None)
|
|
||||||
pickle.load = fake_pickle
|
pickle.load = fake_pickle
|
||||||
builder = ring.RingBuilder.load('fake.builder', open=fake_open)
|
builder = ring.RingBuilder.load('fake.builder', open=fake_open)
|
||||||
self.assertEquals(fake_pickle.call_count, 1)
|
self.assertEquals(fake_pickle.call_count, 1)
|
||||||
fake_open.assert_has_calls([mock.call('fake.builder', 'rb')])
|
fake_open.assert_has_calls([mock.call('fake.builder', 'rb')])
|
||||||
self.assertEquals(builder, rb)
|
self.assertEquals(builder, rb)
|
||||||
fake_pickle.reset_mock()
|
fake_pickle.reset_mock()
|
||||||
fake_open.reset_mock()
|
|
||||||
|
|
||||||
#test old style builder
|
#test old style builder
|
||||||
fake_pickle.return_value = rb.to_dict()
|
fake_pickle.return_value = rb.to_dict()
|
||||||
@ -932,7 +941,6 @@ class TestRingBuilder(unittest.TestCase):
|
|||||||
fake_open.assert_has_calls([mock.call('fake.builder', 'rb')])
|
fake_open.assert_has_calls([mock.call('fake.builder', 'rb')])
|
||||||
self.assertEquals(builder.devs, rb.devs)
|
self.assertEquals(builder.devs, rb.devs)
|
||||||
fake_pickle.reset_mock()
|
fake_pickle.reset_mock()
|
||||||
fake_open.reset_mock()
|
|
||||||
|
|
||||||
#test old devs but no meta
|
#test old devs but no meta
|
||||||
no_meta_builder = rb
|
no_meta_builder = rb
|
||||||
@ -943,10 +951,48 @@ class TestRingBuilder(unittest.TestCase):
|
|||||||
builder = ring.RingBuilder.load('fake.builder', open=fake_open)
|
builder = ring.RingBuilder.load('fake.builder', open=fake_open)
|
||||||
fake_open.assert_has_calls([mock.call('fake.builder', 'rb')])
|
fake_open.assert_has_calls([mock.call('fake.builder', 'rb')])
|
||||||
self.assertEquals(builder.devs, rb.devs)
|
self.assertEquals(builder.devs, rb.devs)
|
||||||
fake_pickle.reset_mock()
|
|
||||||
|
#test an empty builder
|
||||||
|
fake_pickle.side_effect = EOFError
|
||||||
|
pickle.load = fake_pickle
|
||||||
|
self.assertRaises(exceptions.UnPicklingError,
|
||||||
|
ring.RingBuilder.load, 'fake.builder',
|
||||||
|
open=fake_open)
|
||||||
|
|
||||||
|
#test a corrupted builder
|
||||||
|
fake_pickle.side_effect = pickle.UnpicklingError
|
||||||
|
pickle.load = fake_pickle
|
||||||
|
self.assertRaises(exceptions.UnPicklingError,
|
||||||
|
ring.RingBuilder.load, 'fake.builder',
|
||||||
|
open=fake_open)
|
||||||
|
|
||||||
|
#test some error
|
||||||
|
fake_pickle.side_effect = AttributeError
|
||||||
|
pickle.load = fake_pickle
|
||||||
|
self.assertRaises(exceptions.UnPicklingError,
|
||||||
|
ring.RingBuilder.load, 'fake.builder',
|
||||||
|
open=fake_open)
|
||||||
finally:
|
finally:
|
||||||
pickle.load = real_pickle
|
pickle.load = real_pickle
|
||||||
|
|
||||||
|
#test non existent builder file
|
||||||
|
fake_open.side_effect = io_error_not_found
|
||||||
|
self.assertRaises(exceptions.FileNotFoundError,
|
||||||
|
ring.RingBuilder.load, 'fake.builder',
|
||||||
|
open=fake_open)
|
||||||
|
|
||||||
|
#test non accessible builder file
|
||||||
|
fake_open.side_effect = io_error_no_perm
|
||||||
|
self.assertRaises(exceptions.PermissionError,
|
||||||
|
ring.RingBuilder.load, 'fake.builder',
|
||||||
|
open=fake_open)
|
||||||
|
|
||||||
|
#test an error other then ENOENT and ENOPERM
|
||||||
|
fake_open.side_effect = io_error_generic
|
||||||
|
self.assertRaises(IOError,
|
||||||
|
ring.RingBuilder.load, 'fake.builder',
|
||||||
|
open=fake_open)
|
||||||
|
|
||||||
def test_save_load(self):
|
def test_save_load(self):
|
||||||
rb = ring.RingBuilder(8, 3, 1)
|
rb = ring.RingBuilder(8, 3, 1)
|
||||||
devs = [{'id': 0, 'region': 0, 'zone': 0, 'weight': 1,
|
devs = [{'id': 0, 'region': 0, 'zone': 0, 'weight': 1,
|
||||||
|
Loading…
Reference in New Issue
Block a user