def test_avl_is_balancing_correctly(self): """ Test that it is balancing correctly """ avl = AVLTree() point = VirtualPoint("10.128.20.1", 20) avl.insert(20, point) point = VirtualPoint("10.128.20.2", 4) avl.insert(4, point) point = VirtualPoint("10.128.20.3", 3) avl.insert(3, point) point = VirtualPoint("10.128.20.4", 9) avl.insert(9, point) point = VirtualPoint("10.128.20.5", 10) avl.insert(10, point) point = VirtualPoint("10.128.20.6", 15) avl.insert(15, point) bal = avl.is_balanced() self.assertEqual(bal, True)
def test_avl_is_removing_and_balancing_correctly(self): """ Test that it is balancing correctly after removal of a node """ avl = AVLTree() point = VirtualPoint("10.128.20.1", 20) avl.insert(20, point) point = VirtualPoint("10.128.20.2", 4) avl.insert(4, point) point = VirtualPoint("10.128.20.3", 3) avl.insert(3, point) point = VirtualPoint("10.128.20.4", 9) avl.insert(9, point) point = VirtualPoint("10.128.20.5", 10) avl.insert(10, point) point = VirtualPoint("10.128.20.6", 15) avl.insert(15, point) avl.remove(20) # Will cause tree to rebalance bal = avl.is_balanced() self.assertEqual(bal, True)
def test_avl_postorder_traversal(self): """ Test that it traverses tree in post-order correctly """ avl = AVLTree() point = VirtualPoint("10.128.20.1", 20) avl.insert(20, point) point = VirtualPoint("10.128.20.2", 4) avl.insert(4, point) point = VirtualPoint("10.128.20.3", 3) avl.insert(3, point) point = VirtualPoint("10.128.20.4", 9) avl.insert(9, point) point = VirtualPoint("10.128.20.5", 10) avl.insert(10, point) point = VirtualPoint("10.128.20.6", 15) avl.insert(15, point) keys = avl.postorder_traverse() # Expected order of keys when post-order traversal applied expected_key_order = [3, 9, 4, 15, 20, 10] self.assertEqual(keys, expected_key_order)
class Ring(): """ The `Ring` object represents the consistent hashing ring. The `node_config` parameter defaults to `None`, however it should be provided by the `Cache` object The `replicas` parameter adds replicas for a virtual point in the tree. This defaults to `1` and it is not recommended to change this. """ def __init__(self, node_config=None, replicas=1): self.replicas = replicas self.ring = AVLTree() if node_config: with open(node_config, 'r') as nodes: for node in nodes.readlines(): node = node.strip() self.add(node) def __len__(self): return len(self.ring.get_nodes()) def __str__(self): return self.ring.__str__() def add(self, node): """ The `add()` method adds a GhostDB node to the consistent hashing ring. """ for i in range(self.replicas): key = self.key_hash(node, i) vp = VirtualPoint(node, key) self.ring.insert(key, vp) def delete(self, node): """ The `delete()` method removes a GhostDB node from the consistent hashing ring. This is typically performed if the GhostDB node is unreachable. """ for i in range(self.replicas): key = self.key_hash(node, i) self.ring.remove(key) def get_point_for(self, key): """ The `get_point_for()` method returns the correct GhostDB node to send a request to for a given key. """ if len(self) == 0: return None key = self.key_hash(key) node_key, node_value = self.ring.next_gte_pair(key) if not node_value: node_key, node_value = self.ring.minimum_pair() return node_value def get_points(self): """ The `get_points()` method returns all GhostDB nodes in the consistent hashing ring. """ return self.ring.get_nodes() def key_hash(self, key, index=None): """ The `key_hash()` method generates and returns the unsigned CRC32 hash for a provided key in hexidecimal form. """ if index: key = "{:s}:{:d}".format(key, index) s = binascii.crc32(bytes(key, 'utf-8')) return hex(s)