diff --git a/swift/cli/ringbuilder.py b/swift/cli/ringbuilder.py index eac586e267..f5c8c14792 100755 --- a/swift/cli/ringbuilder.py +++ b/swift/cli/ringbuilder.py @@ -1073,8 +1073,7 @@ swift-ring-builder write_builder [min_part_hours] '_last_part_gather_start': 0, '_remove_devs': [], } - builder = RingBuilder(1, 1, 1) - builder.copy_from(builder_dict) + builder = RingBuilder.from_dict(builder_dict) for parts in builder._replica2part2dev: for dev_id in parts: builder.devs[dev_id]['parts'] += 1 diff --git a/swift/common/ring/builder.py b/swift/common/ring/builder.py index 6672fdbecc..c0a37f8a54 100644 --- a/swift/common/ring/builder.py +++ b/swift/common/ring/builder.py @@ -21,6 +21,7 @@ import logging import math import random import cPickle as pickle +from copy import deepcopy from array import array from collections import defaultdict @@ -125,6 +126,12 @@ class RingBuilder(object): 'ring, or all devices have been ' 'deleted') + @classmethod + def from_dict(cls, builder_data): + b = cls(1, 1, 1) # Dummy values + b.copy_from(builder_data) + return b + def copy_from(self, builder): """ Reinitializes this RingBuilder instance from data obtained from the @@ -173,6 +180,11 @@ class RingBuilder(object): for dev in self._iter_devs(): dev.setdefault("region", 1) + def __deepcopy__(self, memo): + the_copy = type(self).from_dict(deepcopy(self.to_dict(), memo)) + memo[id(self)] = the_copy + return the_copy + def to_dict(self): """ Returns a dict that can be used later with copy_from to diff --git a/test/unit/common/ring/test_builder.py b/test/unit/common/ring/test_builder.py index e2dc80824c..a05823368c 100644 --- a/test/unit/common/ring/test_builder.py +++ b/test/unit/common/ring/test_builder.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import copy import errno import mock import operator @@ -84,6 +85,26 @@ class TestRingBuilder(unittest.TestCase): ring.RingBuilder(8, 3, 0) # passes by not crashing self.assertRaises(ValueError, ring.RingBuilder, 8, 3, -1) + def test_deepcopy(self): + rb = ring.RingBuilder(8, 3, 1) + rb.add_dev({'id': 0, 'region': 0, 'zone': 0, 'weight': 1, + 'ip': '127.0.0.1', 'port': 10000, 'device': 'sda1'}) + rb.add_dev({'id': 1, 'region': 0, 'zone': 1, 'weight': 1, + 'ip': '127.0.0.1', 'port': 10001, 'device': 'sda1'}) + rb.add_dev({'id': 2, 'region': 0, 'zone': 2, 'weight': 1, + 'ip': '127.0.0.1', 'port': 10002, 'device': 'sda1'}) + rb.add_dev({'id': 3, 'region': 0, 'zone': 1, 'weight': 1, + 'ip': '127.0.0.1', 'port': 10004, 'device': 'sda1'}) + rb.rebalance() + rb_copy = copy.deepcopy(rb) + + self.assertEqual(rb.to_dict(), rb_copy.to_dict()) + self.assertTrue(rb.devs is not rb_copy.devs) + self.assertTrue(rb._replica2part2dev is not rb_copy._replica2part2dev) + self.assertTrue(rb._last_part_moves is not rb_copy._last_part_moves) + self.assertTrue(rb._remove_devs is not rb_copy._remove_devs) + self.assertTrue(rb._dispersion_graph is not rb_copy._dispersion_graph) + def test_get_ring(self): rb = ring.RingBuilder(8, 3, 1) rb.add_dev({'id': 0, 'region': 0, 'zone': 0, 'weight': 1,