diff --git a/setup.cfg b/setup.cfg index 19121b49cf..9f37ddbd18 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] @@ -91,6 +90,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()