summaryrefslogtreecommitdiff
path: root/ironic
diff options
context:
space:
mode:
authorDavid Shrewsbury <shrewsbury.dave@gmail.com>2014-10-16 15:34:32 -0400
committerDavid Shrewsbury <shrewsbury.dave@gmail.com>2014-10-20 11:53:33 -0400
commite2c1ebcb7f8dc4d76b3e413d060edde96b52e8b3 (patch)
tree1d90686cb875bc0ca2644deaf1d17ed093ad7390 /ironic
parente6ac648e41f6316b81bdc3d7aec8fd86f7857efa (diff)
downloadironic-e2c1ebcb7f8dc4d76b3e413d060edde96b52e8b3.tar.gz
Improve hash ring value conversion
The current method for computing the point on the hash ring converts the MD5 digest into a 4-byte integer (and assumes big-endian). The digest is actually a 16-byte value, so we are eliminating much of the value, thus increasing the possibility of hash collisions. This patch does two things: * Removes the endianness assumption * Uses the full value of the digest to create a long integer, which has unlimited precision in Python. Change-Id: I554ec46f506cb8cdfd5878c11a905a3acfe92db0
Diffstat (limited to 'ironic')
-rw-r--r--ironic/common/hash_ring.py14
-rw-r--r--ironic/tests/test_hash_ring.py17
2 files changed, 27 insertions, 4 deletions
diff --git a/ironic/common/hash_ring.py b/ironic/common/hash_ring.py
index a79ff344d..51ab38aa1 100644
--- a/ironic/common/hash_ring.py
+++ b/ironic/common/hash_ring.py
@@ -15,7 +15,6 @@
import bisect
import hashlib
-import struct
import threading
from oslo.config import cfg
@@ -91,16 +90,23 @@ class HashRing(object):
key_hash = hashlib.md5(key)
for p in range(2 ** CONF.hash_partition_exponent):
key_hash.update(key)
- hashed_key = struct.unpack_from('>I', key_hash.digest())[0]
+ hashed_key = self._hash2int(key_hash)
self._host_hashes[hashed_key] = host
# Gather the (possibly colliding) resulting hashes into a bisectable
# list.
self._partitions = sorted(self._host_hashes.keys())
+ def _hash2int(self, key_hash):
+ """Convert the given hash's digest to a numerical value for the ring.
+
+ :returns: An integer equivalent value of the digest.
+ """
+ return int(key_hash.hexdigest(), 16)
+
def _get_partition(self, data):
try:
- hashed_key = struct.unpack_from(
- '>I', hashlib.md5(data).digest())[0]
+ key_hash = hashlib.md5(data)
+ hashed_key = self._hash2int(key_hash)
position = bisect.bisect(self._partitions, hashed_key)
return position if position < len(self._partitions) else 0
except TypeError:
diff --git a/ironic/tests/test_hash_ring.py b/ironic/tests/test_hash_ring.py
index 8dadb34a7..414da9432 100644
--- a/ironic/tests/test_hash_ring.py
+++ b/ironic/tests/test_hash_ring.py
@@ -13,6 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+import hashlib
+
+import mock
from oslo.config import cfg
from testtools import matchers
@@ -33,6 +36,20 @@ class HashRingTestCase(base.TestCase):
# fake -> foo, bar, baz
# fake-again -> bar, baz, foo
+ @mock.patch.object(hashlib, 'md5')
+ def test__hash2int_returns_int(self, mock_md5):
+ CONF.set_override('hash_partition_exponent', 0)
+ r1 = 32 * 'a'
+ r2 = 32 * 'b'
+ mock_md5.return_value.hexdigest.side_effect = [r1, r2]
+
+ hosts = ['foo', 'bar']
+ replicas = 1
+ ring = hash_ring.HashRing(hosts, replicas=replicas)
+
+ self.assertIn(int(r1, 16), ring._host_hashes)
+ self.assertIn(int(r2, 16), ring._host_hashes)
+
def test_create_ring(self):
hosts = ['foo', 'bar']
replicas = 2