Past fleet state as a param during learning; only learn if there's a difference.

pull/559/head
jMyles 2018-11-12 12:41:40 -08:00
parent 1635f00626
commit 31f66cf071
5 changed files with 53 additions and 34 deletions

View File

@ -104,19 +104,26 @@ class RestMiddleware:
url,
certificate_filepath,
announce_nodes=None,
nodes_i_need=None):
nodes_i_need=None,
client=requests,
fleet_checksum=None):
if nodes_i_need:
# TODO: This needs to actually do something.
# Include node_ids in the request; if the teacher node doesn't know about the
# nodes matching these ids, then it will ask other nodes.
pass
if fleet_checksum:
params = {'fleet': fleet_checksum}
else:
params = {}
if announce_nodes:
payload = bytes().join(bytes(n) for n in announce_nodes)
response = requests.post("https://{}/node_metadata".format(url),
response = client.post("https://{}/node_metadata".format(url),
verify=certificate_filepath,
data=payload, timeout=2)
data=payload, timeout=2, params=params)
else:
response = requests.get("https://{}/node_metadata".format(url),
verify=certificate_filepath, timeout=2)
response = client.get("https://{}/node_metadata".format(url),
verify=certificate_filepath, timeout=2, params=params)
return response

View File

@ -542,7 +542,8 @@ class Learner:
response = self.network_middleware.get_nodes_via_rest(url=rest_url,
nodes_i_need=self._node_ids_to_learn_about_immediately,
announce_nodes=announce_nodes,
certificate_filepath=certificate_filepath)
certificate_filepath=certificate_filepath,
fleet_checksum=self.known_nodes.checksum)
except requests.exceptions.ConnectionError as e:
unresponsive_nodes.add(current_teacher)
teacher_rest_info = current_teacher.rest_information()[0]
@ -550,9 +551,9 @@ class Learner:
# TODO: This error isn't necessarily "no repsonse" - let's maybe pass on the text of the exception here.
self.log.info("No Response from teacher: {}:{}.".format(teacher_rest_info.host, teacher_rest_info.port))
self.cycle_teacher_node()
return
raise False
if response.status_code != 200:
if response.status_code not in (200, 204):
raise RuntimeError("Bad response from teacher: {} - {}".format(response, response.content))
signature, node_payload = signature_splitter(response.content, return_remainder=True)
@ -561,12 +562,20 @@ class Learner:
self.verify_from(current_teacher, node_payload, signature=signature)
except current_teacher.InvalidSignature:
# TODO: What to do if the teacher improperly signed the node payload?
raise
raise False
fleet_state_checksum_bytes, fleet_state_updated_bytes, nodes = FleetState.snapshot_splitter(node_payload, return_remainder=True)
current_teacher.last_seen = maya.now()
current_teacher.update_snapshot(checksum=fleet_state_checksum_bytes.hex(),
updated=maya.MayaDT(int.from_bytes(fleet_state_updated_bytes, byteorder="big")))
self.cycle_teacher_node()
# TODO: This doesn't make sense - a decentralized node can still learn about a federated-only node.
from nucypher.characters.lawful import Ursula
if response.status_code == 204:
return constants.FLEET_STATES_MATCH
node_list = Ursula.batch_from_bytes(nodes, federated_only=self.federated_only) # TODO: 466
new_nodes = []
@ -595,11 +604,6 @@ class Learner:
self._adjust_learning(new_nodes)
learning_round_log_message = "Learning round {}. Teacher: {} knew about {} nodes, {} were new."
current_teacher.last_seen = maya.now()
current_teacher.update_snapshot(checksum=fleet_state_checksum_bytes.hex(),
updated=maya.MayaDT(int.from_bytes(fleet_state_updated_bytes, byteorder="big")))
self.cycle_teacher_node()
self.log.info(learning_round_log_message.format(self._learning_round,
current_teacher,
len(node_list),

View File

@ -169,10 +169,20 @@ class ProxyRESTRoutes:
return Response(bytes(signature) + payload, headers=headers)
def node_metadata_exchange(self, request: Request, query_params: QueryParams):
# If these nodes already have the same fleet state, no exchange is necessary.
learner_fleet_state = query_params.get('fleet')
if learner_fleet_state == self._node_tracker.checksum:
self.log.debug("Learner already knew fleet state {}; doing nothing.".format(learner_fleet_state))
headers = {'Content-Type': 'application/octet-stream'}
payload = self._node_tracker.snapshot()
signature = self._stamp(payload)
return Response(bytes(signature) + payload, headers=headers, status_code=204)
nodes = self._node_class.batch_from_bytes(request.body,
federated_only=self.federated_only, # TODO: 466
)
# TODO: This logic is basically repeated in learn_from_teacher_node. Let's find a better way.
# TODO: This logic is basically repeated in learn_from_teacher_node and remember_node. Let's find a better way. 555
for node in nodes:
if node.checksum_public_address in self._node_tracker:

View File

@ -80,24 +80,8 @@ class MockRestMiddleware(RestMiddleware):
response = mock_client.get("http://localhost/public_information")
return response
def get_nodes_via_rest(self, url, certificate_filepath, announce_nodes=None, nodes_i_need=None):
mock_client = self._get_mock_client_by_url(url)
if nodes_i_need:
# TODO: This needs to actually do something.
# Include node_ids in the request; if the teacher node doesn't know about the
# nodes matching these ids, then it will ask other nodes.
pass
if announce_nodes:
response = mock_client.post("https://{}/node_metadata".format(url),
verify=certificate_filepath,
data=bytes().join(bytes(n) for n in announce_nodes))
else:
response = mock_client.get("https://{}/node_metadata".format(url),
verify=certificate_filepath)
return response
def get_nodes_via_rest(self, url, *args, **kwargs):
return super().get_nodes_via_rest(url, client=self._get_mock_client_by_url(url), *args, **kwargs)
def put_treasure_map_on_node(self, node, map_id, map_payload):
mock_client = self._get_mock_client_by_ursula(node)

View File

@ -1,3 +1,6 @@
from constant_sorrow.constants import FLEET_STATES_MATCH
def test_all_nodes_have_same_fleet_state(federated_ursulas):
checksums = [u.known_nodes.checksum for u in federated_ursulas]
assert len(set(checksums)) == 1 # There is only 1 unique value.
@ -17,4 +20,15 @@ def test_teacher_nodes_cycle(federated_ursulas):
ursula.learn_from_teacher_node()
second_teacher = ursula._current_teacher_node
assert first_teacher != second_teacher
assert first_teacher != second_teacher
def test_nodes_with_equal_fleet_state_do_not_send_anew(federated_ursulas):
some_ursula = list(federated_ursulas)[2]
another_ursula = list(federated_ursulas)[3]
# These two have the same fleet state.
assert some_ursula.known_nodes.checksum == another_ursula.known_nodes.checksum
some_ursula._current_teacher_node = another_ursula
result = some_ursula.learn_from_teacher_node()
assert result is FLEET_STATES_MATCH