From 24657b2b39ae56fcd2b8a8089658ead84f9897c8 Mon Sep 17 00:00:00 2001 From: Christian Schwede Date: Wed, 5 Feb 2014 06:51:46 +0000 Subject: [PATCH] Add tests for swift-ring-builder Add some tests for essential methods in swift-ring-builder. Tests for removing or changing device settings are executed with different search values to cover many possible command line arguments. Currently tested methods: - create ring - add device - remove device - set weight - set info - set min_part_hours - set replicas Tests use swift.common.ring.RingBuilder to verify actions. Catching and testing output from print statements is not tested, because this requires redirecting sys.stdout during tests and that might have some sideeffects for testing tools. bin/swift-ring-builder has been moved to swift/cli/ringbuilder.py and slightly modified to work as before (mainly due to no more existing global variables since that part of the code has been moved inside a main() function). Change-Id: Ia63f59a8faca1fad990784f27532ca07a2125454 --- setup.cfg | 2 +- .../cli/ringbuilder.py | 18 +- test/unit/cli/test_ringbuilder.py | 204 ++++++++++++++++++ 3 files changed, 221 insertions(+), 3 deletions(-) rename bin/swift-ring-builder => swift/cli/ringbuilder.py (98%) create mode 100644 test/unit/cli/test_ringbuilder.py diff --git a/setup.cfg b/setup.cfg index 38b828ac05..f42cf24fa4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -53,7 +53,6 @@ scripts = bin/swift-orphans bin/swift-proxy-server bin/swift-recon-cron - bin/swift-ring-builder bin/swift-temp-url [entry_points] @@ -90,6 +89,7 @@ paste.filter_factory = console_scripts = swift-recon = swift.cli.recon:main + swift-ring-builder = swift.cli.ringbuilder:main [build_sphinx] all_files = 1 diff --git a/bin/swift-ring-builder b/swift/cli/ringbuilder.py similarity index 98% rename from bin/swift-ring-builder rename to swift/cli/ringbuilder.py index 4eaa732b78..a1af20ae54 100755 --- a/bin/swift-ring-builder +++ b/swift/cli/ringbuilder.py @@ -20,7 +20,7 @@ from itertools import islice, izip from math import ceil from os import mkdir from os.path import basename, abspath, dirname, exists, join as pathjoin -from sys import argv, exit, stderr +from sys import argv as sys_argv, exit, stderr from textwrap import wrap from time import time @@ -37,6 +37,9 @@ EXIT_SUCCESS = 0 EXIT_WARNING = 1 EXIT_ERROR = 2 +global argv, backup_dir, builder, builder_file, ring_file +argv = backup_dir = builder = builder_file = ring_file = None + def format_device(dev): """ @@ -796,7 +799,14 @@ swift-ring-builder set_replicas builder.save(argv[1]) exit(EXIT_SUCCESS) -if __name__ == '__main__': + +def main(arguments=None): + global argv, backup_dir, builder, builder_file, ring_file + if arguments: + argv = arguments + else: + argv = sys_argv + if len(argv) < 2: print "swift-ring-builder %(MAJOR_VERSION)s.%(MINOR_VERSION)s\n" % \ globals() @@ -846,3 +856,7 @@ if __name__ == '__main__': exit(2) else: Commands.__dict__.get(command, Commands.unknown.im_func)() + + +if __name__ == '__main__': + main() diff --git a/test/unit/cli/test_ringbuilder.py b/test/unit/cli/test_ringbuilder.py new file mode 100644 index 0000000000..eb2d3b6726 --- /dev/null +++ b/test/unit/cli/test_ringbuilder.py @@ -0,0 +1,204 @@ +# Copyright (c) 2014 Christian Schwede +# +# 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 os +import tempfile +import unittest + +import swift.cli.ringbuilder +from swift.common.ring import RingBuilder + + +class TestCommands(unittest.TestCase): + + def __init__(self, *args, **kwargs): + super(TestCommands, self).__init__(*args, **kwargs) + + # List of search values for various actions + # These should all match the first device in the sample ring + # (see below) but not the second device + self.search_values = ["d0", "/sda1", "r0", "z0", "z0-127.0.0.1", + "127.0.0.1", "z0:6000", ":6000", "R127.0.0.1", + "127.0.0.1R127.0.0.1", "R:6000", + "_some meta data"] + tmpf = tempfile.NamedTemporaryFile() + self.tmpfile = tmpf.name + + def tearDown(self): + try: + os.remove(self.tmpfile) + except OSError: + pass + + def create_sample_ring(self): + """ Create a sample ring with two devices + + At least two devices are needed to test removing + a device, since removing the last device of a ring + is not allowed """ + + # Ensure there is no existing test builder file because + # create_sample_ring() might be used more than once in a single test + try: + os.remove(self.tmpfile) + except OSError: + pass + + ring = RingBuilder(6, 3, 1) + ring.add_dev({'weight': 100.0, + 'region': 0, + 'zone': 0, + 'ip': '127.0.0.1', + 'port': 6000, + 'device': 'sda1', + 'meta': 'some meta data', + }) + ring.add_dev({'weight': 100.0, + 'region': 1, + 'zone': 1, + 'ip': '127.0.0.2', + 'port': 6001, + 'device': 'sda2' + }) + ring.save(self.tmpfile) + + def test_create_ring(self): + argv = ["", self.tmpfile, "create", "6", "3.14159265359", "1"] + self.assertRaises(SystemExit, swift.cli.ringbuilder.main, argv) + ring = RingBuilder.load(self.tmpfile) + self.assertEqual(ring.part_power, 6) + self.assertEqual(ring.replicas, 3.14159265359) + self.assertEqual(ring.min_part_hours, 1) + + def test_add_device(self): + self.create_sample_ring() + argv = ["", self.tmpfile, "add", + "r2z3-127.0.0.1:6000/sda3_some meta data", "3.14159265359"] + self.assertRaises(SystemExit, swift.cli.ringbuilder.main, argv) + + # Check that device was created with given data + ring = RingBuilder.load(self.tmpfile) + dev = [d for d in ring.devs if d['id'] == 2][0] + self.assertEqual(dev['region'], 2) + self.assertEqual(dev['zone'], 3) + self.assertEqual(dev['ip'], '127.0.0.1') + self.assertEqual(dev['port'], 6000) + self.assertEqual(dev['device'], 'sda3') + self.assertEqual(dev['weight'], 3.14159265359) + self.assertEqual(dev['replication_ip'], '127.0.0.1') + self.assertEqual(dev['replication_port'], 6000) + self.assertEqual(dev['meta'], 'some meta data') + + # Final check, rebalance and check ring is ok + ring.rebalance() + self.assertTrue(ring.validate()) + + def test_remove_device(self): + for search_value in self.search_values: + self.create_sample_ring() + argv = ["", self.tmpfile, "remove", search_value] + self.assertRaises(SystemExit, swift.cli.ringbuilder.main, argv) + ring = RingBuilder.load(self.tmpfile) + + # Check that weight was set to 0 + dev = [d for d in ring.devs if d['id'] == 0][0] + self.assertEqual(dev['weight'], 0) + + # Check that device is in list of devices to be removed + dev = [d for d in ring._remove_devs if d['id'] == 0][0] + self.assertEqual(dev['region'], 0) + self.assertEqual(dev['zone'], 0) + self.assertEqual(dev['ip'], '127.0.0.1') + self.assertEqual(dev['port'], 6000) + self.assertEqual(dev['device'], 'sda1') + self.assertEqual(dev['weight'], 0) + self.assertEqual(dev['replication_ip'], '127.0.0.1') + self.assertEqual(dev['replication_port'], 6000) + self.assertEqual(dev['meta'], 'some meta data') + + # Check that second device in ring is not affected + dev = [d for d in ring.devs if d['id'] == 1][0] + self.assertEqual(dev['weight'], 100) + self.assertFalse([d for d in ring._remove_devs if d['id'] == 1]) + + # Final check, rebalance and check ring is ok + ring.rebalance() + self.assertTrue(ring.validate()) + + def test_set_weight(self): + for search_value in self.search_values: + self.create_sample_ring() + + argv = ["", self.tmpfile, "set_weight", + search_value, "3.14159265359"] + self.assertRaises(SystemExit, swift.cli.ringbuilder.main, argv) + ring = RingBuilder.load(self.tmpfile) + + # Check that weight was changed + dev = [d for d in ring.devs if d['id'] == 0][0] + self.assertEqual(dev['weight'], 3.14159265359) + + # Check that second device in ring is not affected + dev = [d for d in ring.devs if d['id'] == 1][0] + self.assertEqual(dev['weight'], 100) + + # Final check, rebalance and check ring is ok + ring.rebalance() + self.assertTrue(ring.validate()) + + def test_set_info(self): + for search_value in self.search_values: + + self.create_sample_ring() + argv = ["", self.tmpfile, "set_info", search_value, + "127.0.1.1:8000/sda1_other meta data"] + self.assertRaises(SystemExit, swift.cli.ringbuilder.main, argv) + + # Check that device was created with given data + ring = RingBuilder.load(self.tmpfile) + dev = [d for d in ring.devs if d['id'] == 0][0] + self.assertEqual(dev['ip'], '127.0.1.1') + self.assertEqual(dev['port'], 8000) + self.assertEqual(dev['device'], 'sda1') + self.assertEqual(dev['meta'], 'other meta data') + + # Check that second device in ring is not affected + dev = [d for d in ring.devs if d['id'] == 1][0] + self.assertEqual(dev['ip'], '127.0.0.2') + self.assertEqual(dev['port'], 6001) + self.assertEqual(dev['device'], 'sda2') + self.assertEqual(dev['meta'], '') + + # Final check, rebalance and check ring is ok + ring.rebalance() + self.assertTrue(ring.validate()) + + def test_set_min_part_hours(self): + self.create_sample_ring() + argv = ["", self.tmpfile, "set_min_part_hours", "24"] + self.assertRaises(SystemExit, swift.cli.ringbuilder.main, argv) + ring = RingBuilder.load(self.tmpfile) + self.assertEqual(ring.min_part_hours, 24) + + def test_set_replicas(self): + self.create_sample_ring() + argv = ["", self.tmpfile, "set_replicas", "3.14159265359"] + self.assertRaises(SystemExit, swift.cli.ringbuilder.main, argv) + ring = RingBuilder.load(self.tmpfile) + self.assertEqual(ring.replicas, 3.14159265359) + + +if __name__ == '__main__': + unittest.main()