GitLab is used only for code review, issue tracking and project management. Canonical locations for source code are still and

Commit 30334218 authored by juga  's avatar juga
Browse files

Merge branch 'bug_33009_v6' into 'maint-1.1'

Bug 33009 v6

See merge request torproject/network-health/sbws!6
parents 3efb6867 20df35c4
......@@ -211,10 +211,16 @@ def _pick_ideal_second_hop(relay, dest, rl, cont, is_exit):
else rl.non_exits
if not len(candidates):
return None
min_relay_bw = rl.exit_min_bw() if is_exit else rl.non_exit_min_bw()
log.debug('Picking a 2nd hop to measure %s from %d choices. is_exit=%s',
relay.nickname, len(candidates), is_exit)
for min_bw_factor in [2, 1.75, 1.5, 1.25, 1]:
min_bw = relay.consensus_bandwidth * min_bw_factor
# We might have a really slow/new relay. Try to measure it properly by
# using only relays with or above our calculated min_relay_bw (see:
# _calculate_min_bw_second_hop() in
if min_bw < min_relay_bw:
min_bw = min_relay_bw
new_candidates = stem_utils.only_relays_with_bandwidth(
cont, candidates, min_bw=min_bw)
if len(new_candidates) > 0:
......@@ -278,6 +278,9 @@ class RelayList:
# Start with 0 for the min bw for our second hops
self._exit_min_bw = 0
self._non_exit_min_bw = 0
def _need_refresh(self):
......@@ -431,6 +434,10 @@ class RelayList:
int(self._measurements_period / 24 / 60 / 60),
# Calculate minimum bandwidth value for 2nd hop after we refreshed
# our available relays.
def recent_consensus_count(self):
"""Number of times a new consensus was obtained."""
......@@ -455,3 +462,31 @@ class RelayList:
def recent_measurement_attempt_count(self):
return len(self._recent_measurement_attempt)
def _calculate_min_bw_second_hop(self):
Calculates the minimum bandwidth for both exit and non-exit relays
chosen as a second hop by picking the lowest bandwidth value available
from the top 75% of the respective category.
# Sort our sets of candidates according to bw, lowest amount first.
# It's okay to keep things simple for the calculation and go over all
# exits, including badexits.
exit_candidates = sorted(self.exits,
key=lambda r: r.consensus_bandwidth)
non_exit_candidates = sorted(self.non_exits,
key=lambda r: r.consensus_bandwidth)
# We know the bandwidth is sorted from least to most. Dividing the
# length of the available relays by 4 gives us the position of the
# relay with the lowest bandwidth from the top 75%. We do this both
# for our exit and non-exit candidates.
pos = int(len(exit_candidates)/4)
self._exit_min_bw = exit_candidates[pos].consensus_bandwidth
pos = int(len(non_exit_candidates)/4)
self._non_exit_min_bw = non_exit_candidates[pos].consensus_bandwidth
def exit_min_bw(self):
return self._exit_min_bw
def non_exit_min_bw(self):
return self._non_exit_min_bw
......@@ -3,6 +3,7 @@ import pytest
import os.path
from unittest import mock
from freezegun import freeze_time
from stem import descriptor
from sbws import settings
......@@ -120,7 +121,8 @@ def router_status(server_descriptor, router_statuses):
def relay_list(args, conf, controller):
"""Returns a RelayList containing the Relays in the controller"""
return relaylist.RelayList(args, conf, controller)
with freeze_time("2020-02-29 10:00:00"):
return relaylist.RelayList(args, conf, controller)
......@@ -18,6 +18,8 @@ def test_init_relays(
Test `init_relays` when creating the RelayList the first time and when a
new consensus is received.
Test that the number of consesus timesamps and relays is correct.
Additionally, make sure the calculated min bw for the second hop for
exit/non-exit relays is correct, too.
state = State(conf['paths']['state_fpath'])
# There is no need to mock datetime to update the consensus, since the
......@@ -31,6 +33,9 @@ def test_init_relays(
# The actual number of relays in the consensus
assert len(relay_list._relays) == 6433
fps = {r.fingerprint for r in relay_list._relays}
# The calculated min bw for the second hop
assert 2100000 == relay_list._exit_min_bw
assert 220000 == relay_list._non_exit_min_bw
# One hour later there is a new consensus
relay_list._controller = controller_1h_later
......@@ -44,6 +49,9 @@ def test_init_relays(
fps_1h_later = {r.fingerprint for r in relay_list._relays}
added_fps = fps_1h_later.difference(fps)
assert 6505 == 6433 + len(added_fps)
# The calculated min bw for the second hop
assert 2120000 == relay_list._exit_min_bw
assert 200000 == relay_list._non_exit_min_bw
# Five days later plus 1 second.
# The first consensus timestamp will get removed.
......@@ -62,6 +70,9 @@ def test_init_relays(
# The number of relays will be the number of relays in the cosensus plus
# the added ones minus the removed ones.
assert 6925 == 6505 + len(added_fps) - len(removed_fps)
# The calculated min bw for the second hop
assert 2790000 == relay_list._exit_min_bw
assert 110000 == relay_list._non_exit_min_bw
def test_increment_recent_measurement_attempt(args, conf, controller):
......@@ -71,9 +82,8 @@ def test_increment_recent_measurement_attempt(args, conf, controller):
It also tests that the state file is updated correctly.
state = State(conf['paths']['state_fpath'])
# For this test it does not matter that the consensus timestamps or relays
# are not correct.
relay_list = RelayList(args, conf, controller=controller, state=state)
with freeze_time("2020-02-29 10:00:00"):
relay_list = RelayList(args, conf, controller=controller, state=state)
# The initial count is 0 and the state does not have that key.
assert 0 == relay_list.recent_measurement_attempt_count
assert not state.get("recent_measurement_attempt", None)
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment