def test_should_return_routing_table_on_valid_record_with_extra_role(self): table = RoutingTable.parse_routing_info(VALID_ROUTING_RECORD_WITH_EXTRA_ROLE["servers"], VALID_ROUTING_RECORD_WITH_EXTRA_ROLE["ttl"]) assert table.routers == {('127.0.0.1', 9001), ('127.0.0.1', 9002), ('127.0.0.1', 9003)} assert table.readers == {('127.0.0.1', 9004), ('127.0.0.1', 9005)} assert table.writers == {('127.0.0.1', 9006)} assert table.ttl == 300
async def get_routing_table(self, context=None): try: result = await self.run( "CALL dbms.cluster.routing.getRoutingTable($context)", {"context": dict(context or {})}) record = await result.single() if not record: raise BoltRoutingError( "Routing table call returned " "no data", self.remote_address) assert isinstance(record, Record) servers = record["servers"] ttl = record["ttl"] log.debug("[#%04X] S: <ROUTING> servers=%r ttl=%r", self.local_address.port_number, servers, ttl) return RoutingTable.parse_routing_info(servers, ttl) except BoltFailure as error: if error.title == "ProcedureNotFound": raise BoltRoutingError("Server does not support " "routing", self.remote_address) from error else: raise except ValueError as error: raise BoltRoutingError("Invalid routing table", self.remote_address) from error
def test_should_return_all_distinct_servers_in_routing_table(self): routing_table = { "ttl": 300, "servers": [ { "role": "ROUTE", "addresses": ["127.0.0.1:9001", "127.0.0.1:9002", "127.0.0.1:9003"] }, { "role": "READ", "addresses": ["127.0.0.1:9001", "127.0.0.1:9005"] }, { "role": "WRITE", "addresses": ["127.0.0.1:9002"] }, ], } table = RoutingTable.parse_routing_info( database=DEFAULT_DATABASE, servers=routing_table["servers"], ttl=routing_table["ttl"], ) assert table.servers() == {('127.0.0.1', 9001), ('127.0.0.1', 9002), ('127.0.0.1', 9003), ('127.0.0.1', 9005)}
def test_should_be_fresh_after_update(self): table = RoutingTable.parse_routing_info( database=DEFAULT_DATABASE, servers=VALID_ROUTING_RECORD["servers"], ttl=VALID_ROUTING_RECORD["ttl"], ) assert table.is_fresh(readonly=True) assert table.is_fresh(readonly=False)
def test_should_become_stale_if_no_writers(self): table = RoutingTable.parse_routing_info( database=DEFAULT_DATABASE, servers=VALID_ROUTING_RECORD["servers"], ttl=VALID_ROUTING_RECORD["ttl"], ) table.writers.clear() assert table.is_fresh(readonly=True) assert not table.is_fresh(readonly=False)
def test_should_return_routing_table_on_valid_record(self): table = RoutingTable.parse_routing_info( database=DEFAULT_DATABASE, servers=VALID_ROUTING_RECORD["servers"], ttl=VALID_ROUTING_RECORD["ttl"], ) assert table.routers == {('127.0.0.1', 9001), ('127.0.0.1', 9002), ('127.0.0.1', 9003)} assert table.readers == {('127.0.0.1', 9004), ('127.0.0.1', 9005)} assert table.writers == {('127.0.0.1', 9006)} assert table.ttl == 300
def fetch_routing_table(self, *, address, timeout, database): """ Fetch a routing table from a given router address. :param address: router address :param timeout: seconds :param database: the database name :type: str :return: a new RoutingTable instance or None if the given router is currently unable to provide routing information :raise neo4j.exceptions.ServiceUnavailable: if no writers are available :raise neo4j._exceptions.BoltProtocolError: if the routing information received is unusable """ new_routing_info = self.fetch_routing_info(address=address, timeout=timeout, database=database) if new_routing_info is None: return None elif not new_routing_info: raise BoltRoutingError("Invalid routing table", address) else: servers = new_routing_info[0]["servers"] ttl = new_routing_info[0]["ttl"] new_routing_table = RoutingTable.parse_routing_info( database=database, servers=servers, ttl=ttl) # Parse routing info and count the number of each type of server num_routers = len(new_routing_table.routers) num_readers = len(new_routing_table.readers) # num_writers = len(new_routing_table.writers) # If no writers are available. This likely indicates a temporary state, # such as leader switching, so we should not signal an error. # No routers if num_routers == 0: raise BoltRoutingError("No routing servers returned from server", address) # No readers if num_readers == 0: raise BoltRoutingError("No read servers returned from server", address) # At least one of each is fine, so return this table return new_routing_table
def fetch_routing_table(self, address): """ Fetch a routing table from a given router address. :param address: router address :return: a new RoutingTable instance or None if the given router is currently unable to provide routing information :raise ServiceUnavailable: if no writers are available :raise ProtocolError: if the routing information received is unusable """ new_routing_info = self.fetch_routing_info(address) if new_routing_info is None: return None elif not new_routing_info: raise BoltRoutingError("Invalid routing table", address) else: servers = new_routing_info[0]["servers"] ttl = new_routing_info[0]["ttl"] new_routing_table = RoutingTable.parse_routing_info(servers, ttl) # Parse routing info and count the number of each type of server num_routers = len(new_routing_table.routers) num_readers = len(new_routing_table.readers) num_writers = len(new_routing_table.writers) # No writers are available. This likely indicates a temporary state, # such as leader switching, so we should not signal an error. # When no writers available, then we flag we are reading in absence of writer self.missing_writer = (num_writers == 0) # No routers if num_routers == 0: raise BoltRoutingError("No routing servers returned from server", address) # No readers if num_readers == 0: raise BoltRoutingError("No read servers returned from server", address) # At least one of each is fine, so return this table return new_routing_table
def test_should_become_stale_if_no_writers(self): table = RoutingTable.parse_routing_info(VALID_ROUTING_RECORD["servers"], VALID_ROUTING_RECORD["ttl"]) table.writers.clear() assert table.is_fresh(readonly=True) assert not table.is_fresh(readonly=False)
def test_should_become_stale_on_expiry(self): table = RoutingTable.parse_routing_info(VALID_ROUTING_RECORD["servers"], VALID_ROUTING_RECORD["ttl"]) table.ttl = 0 assert not table.is_fresh(readonly=True) assert not table.is_fresh(readonly=False)
def test_should_be_fresh_after_update(self): table = RoutingTable.parse_routing_info(VALID_ROUTING_RECORD["servers"], VALID_ROUTING_RECORD["ttl"]) assert table.is_fresh(readonly=True) assert table.is_fresh(readonly=False)