Add ability to save builder data to a disk file

Instances of the RingBuilder class can store its data to a disk file by
the save method and load it by the load method.

blueprint argparse-in-swift-ring-builder

Change-Id: I69fdf0693ca9f520d235a795ecdd2da310dcd5d3
This commit is contained in:
Ilya Kharin 2013-05-16 19:38:42 +04:00
parent 678a3ae832
commit cc040a9c29
3 changed files with 75 additions and 20 deletions

View File

@ -14,7 +14,6 @@
# 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 cPickle as pickle
from array import array from array import array
from errno import EEXIST from errno import EEXIST
from itertools import islice, izip from itertools import islice, izip
@ -74,9 +73,8 @@ swift-ring-builder <builder_file> create <part_power> <replicas>
except OSError, err: except OSError, err:
if err.errno != EEXIST: if err.errno != EEXIST:
raise raise
pickle.dump(builder.to_dict(), open(pathjoin(backup_dir, builder.save(pathjoin(backup_dir, '%d.' % time() + basename(argv[1])))
'%d.' % time() + basename(argv[1])), 'wb'), protocol=2) builder.save(argv[1])
pickle.dump(builder.to_dict(), open(argv[1], 'wb'), protocol=2)
exit(EXIT_SUCCESS) exit(EXIT_SUCCESS)
def default(): def default():
@ -342,7 +340,7 @@ swift-ring-builder <builder_file> add
(region, zone, ip, port, device_name))[0] (region, zone, ip, port, device_name))[0]
print('Device %s with %s weight got id %s' % print('Device %s with %s weight got id %s' %
(format_device(new_dev), weight, new_dev['id'])) (format_device(new_dev), weight, new_dev['id']))
pickle.dump(builder.to_dict(), open(argv[1], 'wb'), protocol=2) builder.save(argv[1])
exit(EXIT_SUCCESS) exit(EXIT_SUCCESS)
def set_weight(): def set_weight():
@ -382,7 +380,7 @@ swift-ring-builder <builder_file> set_weight <search-value> <weight>
builder.set_dev_weight(dev['id'], weight) builder.set_dev_weight(dev['id'], weight)
print '%s weight set to %s' % (format_device(dev), print '%s weight set to %s' % (format_device(dev),
dev['weight']) dev['weight'])
pickle.dump(builder.to_dict(), open(argv[1], 'wb'), protocol=2) builder.save(argv[1])
exit(EXIT_SUCCESS) exit(EXIT_SUCCESS)
def set_info(): def set_info():
@ -500,7 +498,7 @@ swift-ring-builder <builder_file> set_info
dev[key] = value dev[key] = value
print 'Device %s is now %s' % (orig_dev_string, print 'Device %s is now %s' % (orig_dev_string,
format_device(dev)) format_device(dev))
pickle.dump(builder.to_dict(), open(argv[1], 'wb'), protocol=2) builder.save(argv[1])
exit(EXIT_SUCCESS) exit(EXIT_SUCCESS)
def remove(): def remove():
@ -552,7 +550,7 @@ swift-ring-builder <builder_file> remove <search-value> [search-value ...]
print '%s marked for removal and will ' \ print '%s marked for removal and will ' \
'be removed next rebalance.' % format_device(dev) 'be removed next rebalance.' % format_device(dev)
pickle.dump(builder.to_dict(), open(argv[1], 'wb'), protocol=2) builder.save(argv[1])
exit(EXIT_SUCCESS) exit(EXIT_SUCCESS)
def rebalance(): def rebalance():
@ -619,10 +617,9 @@ swift-ring-builder <builder_file> rebalance <seed>
ts = time() ts = time()
builder.get_ring().save( builder.get_ring().save(
pathjoin(backup_dir, '%d.' % ts + basename(ring_file))) pathjoin(backup_dir, '%d.' % ts + basename(ring_file)))
pickle.dump(builder.to_dict(), open(pathjoin(backup_dir, builder.save(pathjoin(backup_dir, '%d.' % ts + basename(argv[1])))
'%d.' % ts + basename(argv[1])), 'wb'), protocol=2)
builder.get_ring().save(ring_file) builder.get_ring().save(ring_file)
pickle.dump(builder.to_dict(), open(argv[1], 'wb'), protocol=2) builder.save(argv[1])
exit(status) exit(status)
def validate(): def validate():
@ -656,7 +653,7 @@ swift-ring-builder <builder_file> write_ring
def pretend_min_part_hours_passed(): def pretend_min_part_hours_passed():
builder.pretend_min_part_hours_passed() builder.pretend_min_part_hours_passed()
pickle.dump(builder.to_dict(), open(argv[1], 'wb'), protocol=2) builder.save(argv[1])
exit(EXIT_SUCCESS) exit(EXIT_SUCCESS)
def set_min_part_hours(): def set_min_part_hours():
@ -672,7 +669,7 @@ swift-ring-builder <builder_file> set_min_part_hours <hours>
builder.change_min_part_hours(int(argv[3])) builder.change_min_part_hours(int(argv[3]))
print 'The minimum number of hours before a partition can be ' \ print 'The minimum number of hours before a partition can be ' \
'reassigned is now set to %s' % argv[3] 'reassigned is now set to %s' % argv[3]
pickle.dump(builder.to_dict(), open(argv[1], 'wb'), protocol=2) builder.save(argv[1])
exit(EXIT_SUCCESS) exit(EXIT_SUCCESS)
def set_replicas(): def set_replicas():
@ -704,7 +701,7 @@ swift-ring-builder <builder_file> set_replicas <replicas>
builder.set_replicas(new_replicas) builder.set_replicas(new_replicas)
print 'The replica count is now %.6f.' % builder.replicas print 'The replica count is now %.6f.' % builder.replicas
print 'The change will take effect after the next rebalance.' print 'The change will take effect after the next rebalance.'
pickle.dump(builder.to_dict(), open(argv[1], 'wb'), protocol=2) builder.save(argv[1])
exit(EXIT_SUCCESS) exit(EXIT_SUCCESS)
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -1000,6 +1000,13 @@ class RingBuilder(object):
dev.setdefault('replication_port', dev['port']) dev.setdefault('replication_port', dev['port'])
return builder return builder
def save(self, builder_file):
"""Serialize this RingBuilder instance to disk.
:param builder_file: path to builder file to save
"""
pickle.dump(self.to_dict(), open(builder_file, 'wb'), protocol=2)
def search_devs(self, search_value): def search_devs(self, search_value):
""" """
The <search-value> can be of the form:: The <search-value> can be of the form::

View File

@ -13,13 +13,13 @@
# 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 operator import operator
import os import os
import unittest import unittest
import cPickle as pickle import cPickle as pickle
from collections import defaultdict from collections import defaultdict
from shutil import rmtree from shutil import rmtree
from mock import Mock, call as mock_call
from swift.common import exceptions from swift.common import exceptions
from swift.common import ring from swift.common import ring
@ -666,12 +666,12 @@ class TestRingBuilder(unittest.TestCase):
real_pickle = pickle.load real_pickle = pickle.load
try: try:
#test a legit builder #test a legit builder
fake_pickle = Mock(return_value=rb) fake_pickle = mock.Mock(return_value=rb)
fake_open = Mock(return_value=None) 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() fake_open.reset_mock()
@ -680,7 +680,7 @@ class TestRingBuilder(unittest.TestCase):
fake_pickle.return_value = rb.to_dict() fake_pickle.return_value = rb.to_dict()
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)
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() fake_open.reset_mock()
@ -692,12 +692,63 @@ class TestRingBuilder(unittest.TestCase):
fake_pickle.return_value = no_meta_builder fake_pickle.return_value = no_meta_builder
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)
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()
finally: finally:
pickle.load = real_pickle pickle.load = real_pickle
def test_save_load(self):
rb = ring.RingBuilder(8, 3, 1)
devs = [{'id': 0, 'region': 0, 'zone': 0, 'weight': 1,
'ip': '127.0.0.0', 'port': 10000,
'replication_ip': '127.0.0.0', 'replication_port': 10000,
'device': 'sda1', 'meta': 'meta0'},
{'id': 1, 'region': 0, 'zone': 1, 'weight': 1,
'ip': '127.0.0.1', 'port': 10001,
'replication_ip': '127.0.0.1', 'replication_port': 10001,
'device': 'sdb1', 'meta': 'meta1'},
{'id': 2, 'region': 0, 'zone': 2, 'weight': 2,
'ip': '127.0.0.2', 'port': 10002,
'replication_ip': '127.0.0.2', 'replication_port': 10002,
'device': 'sdc1', 'meta': 'meta2'},
{'id': 3, 'region': 0, 'zone': 3, 'weight': 2,
'ip': '127.0.0.3', 'port': 10003,
'replication_ip': '127.0.0.3', 'replication_port': 10003,
'device': 'sdd1', 'meta': ''}]
for d in devs:
rb.add_dev(d)
rb.rebalance()
builder_file = os.path.join(self.testdir, 'test_save.builder')
rb.save(builder_file)
loaded_rb = ring.RingBuilder.load(builder_file)
self.maxDiff = None
self.assertEquals(loaded_rb.to_dict(), rb.to_dict())
@mock.patch('__builtin__.open', autospec=True)
@mock.patch('swift.common.ring.builder.pickle.dump', autospec=True)
def test_save(self, mock_pickle_dump, mock_open):
mock_open.return_value = mock_fh = mock.Mock()
rb = ring.RingBuilder(8, 3, 1)
devs = [{'id': 0, 'region': 0, 'zone': 0, 'weight': 1,
'ip': '127.0.0.0', 'port': 10000, 'device': 'sda1',
'meta': 'meta0'},
{'id': 1, 'region': 0, 'zone': 1, 'weight': 1,
'ip': '127.0.0.1', 'port': 10001, 'device': 'sdb1',
'meta': 'meta1'},
{'id': 2, 'region': 0, 'zone': 2, 'weight': 2,
'ip': '127.0.0.2', 'port': 10002, 'device': 'sdc1',
'meta': 'meta2'},
{'id': 3, 'region': 0, 'zone': 3, 'weight': 2,
'ip': '127.0.0.3', 'port': 10003, 'device': 'sdd1'}]
for d in devs:
rb.add_dev(d)
rb.rebalance()
rb.save('some.builder')
mock_open.assert_called_once_with('some.builder', 'wb')
mock_pickle_dump.assert_called_once_with(rb.to_dict(), mock_fh,
protocol=2)
def test_search_devs(self): def test_search_devs(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,