diff --git a/swift/cli/ringbuilder.py b/swift/cli/ringbuilder.py index cfd1694e52..75d2b347a6 100755 --- a/swift/cli/ringbuilder.py +++ b/swift/cli/ringbuilder.py @@ -33,7 +33,7 @@ from six.moves import zip as izip from six.moves import input from swift.common import exceptions -from swift.common.ring import RingBuilder, Ring +from swift.common.ring import RingBuilder, Ring, RingData from swift.common.ring.builder import MAX_BALANCE from swift.common.ring.utils import validate_args, \ validate_and_normalize_ip, build_dev_from_opts, \ @@ -450,6 +450,23 @@ swift-ring-builder timedelta(seconds=builder.min_part_seconds_left))) print('The overload factor is %0.2f%% (%.6f)' % ( builder.overload * 100, builder.overload)) + + # compare ring file against builder file + if not exists(ring_file): + print('Ring file %s not found, ' + 'probably it hasn\'t been written yet' % ring_file) + else: + builder_dict = builder.get_ring().to_dict() + try: + ring_dict = RingData.load(ring_file).to_dict() + except Exception as exc: + print('Ring file %s is invalid: %r' % (ring_file, exc)) + else: + if builder_dict == ring_dict: + print('Ring file %s is up-to-date' % ring_file) + else: + print('Ring file %s is obsolete' % ring_file) + if builder.devs: balance_per_dev = builder._build_balance_per_dev() print('Devices: id region zone ip address port ' diff --git a/test/unit/cli/test_ringbuilder.py b/test/unit/cli/test_ringbuilder.py index 88e081ee85..2cbde67c3f 100644 --- a/test/unit/cli/test_ringbuilder.py +++ b/test/unit/cli/test_ringbuilder.py @@ -16,6 +16,7 @@ import logging import mock import os +import re import six import tempfile import unittest @@ -1741,6 +1742,8 @@ class TestCommands(unittest.TestCase, RunSwiftRingBuilderMixin): "The minimum number of hours before a partition can be " \ "reassigned is 1 (0:00:00 remaining)\n" \ "The overload factor is 0.00%% (0.000000)\n" \ + "Ring file %s.ring.gz not found, probably " \ + "it hasn't been written yet\n" \ "Devices: id region zone ip address port " \ "replication ip replication port name weight " \ "partitions balance flags meta\n" \ @@ -1755,9 +1758,68 @@ class TestCommands(unittest.TestCase, RunSwiftRingBuilderMixin): " 0 -100.00 \n" \ " 3 3 3 127.0.0.4 6003 " \ "127.0.0.4 6003 sdd4 0.00" \ - " 0 0.00 \n" % self.tmpfile + " 0 0.00 \n" % (self.tmpfile, self.tmpfile) self.assertEqual(expected, mock_stdout.getvalue()) + def test_default_ringfile_check(self): + self.create_sample_ring() + + # ring file not created + mock_stdout = six.StringIO() + mock_stderr = six.StringIO() + argv = ["", self.tmpfile] + with mock.patch("sys.stdout", mock_stdout): + with mock.patch("sys.stderr", mock_stderr): + self.assertRaises(SystemExit, ringbuilder.main, argv) + rnf = re.compile("Ring file .*\.ring\.gz not found") + self.assertTrue(rnf.findall(mock_stdout.getvalue())) + + # write ring file + argv = ["", self.tmpfile, "rebalance"] + self.assertRaises(SystemExit, ringbuilder.main, argv) + # ring file is up-to-date + mock_stdout = six.StringIO() + argv = ["", self.tmpfile] + with mock.patch("sys.stdout", mock_stdout): + with mock.patch("sys.stderr", mock_stderr): + self.assertRaises(SystemExit, ringbuilder.main, argv) + rutd = re.compile("Ring file .*\.ring\.gz is up-to-date") + self.assertTrue(rutd.findall(mock_stdout.getvalue())) + + # change builder (set weight) + argv = ["", self.tmpfile, "set_weight", "0", "--id", "3"] + self.assertRaises(SystemExit, ringbuilder.main, argv) + # ring file is obsolete after set_weight + mock_stdout = six.StringIO() + argv = ["", self.tmpfile] + with mock.patch("sys.stdout", mock_stdout): + with mock.patch("sys.stderr", mock_stderr): + self.assertRaises(SystemExit, ringbuilder.main, argv) + ro = re.compile("Ring file .*\.ring\.gz is obsolete") + self.assertTrue(ro.findall(mock_stdout.getvalue())) + + # write ring file + argv = ["", self.tmpfile, "write_ring"] + self.assertRaises(SystemExit, ringbuilder.main, argv) + # ring file up-to-date again + mock_stdout = six.StringIO() + argv = ["", self.tmpfile] + with mock.patch("sys.stdout", mock_stdout): + with mock.patch("sys.stderr", mock_stderr): + self.assertRaises(SystemExit, ringbuilder.main, argv) + self.assertTrue(rutd.findall(mock_stdout.getvalue())) + + # Break ring file e.g. just make it empty + open('%s.ring.gz' % self.tmpfile, 'w').close() + # ring file is invalid + mock_stdout = six.StringIO() + argv = ["", self.tmpfile] + with mock.patch("sys.stdout", mock_stdout): + with mock.patch("sys.stderr", mock_stderr): + self.assertRaises(SystemExit, ringbuilder.main, argv) + ro = re.compile("Ring file .*\.ring\.gz is invalid") + self.assertTrue(ro.findall(mock_stdout.getvalue())) + def test_rebalance(self): self.create_sample_ring() argv = ["", self.tmpfile, "rebalance", "3"]