GitLab is used only for code review, issue tracking and project management. Canonical locations for source code are still https://gitweb.torproject.org/ https://git.torproject.org/ and git-rw.torproject.org.

Commit 30334218 authored by juga  's avatar juga

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): ...@@ -211,10 +211,16 @@ def _pick_ideal_second_hop(relay, dest, rl, cont, is_exit):
else rl.non_exits else rl.non_exits
if not len(candidates): if not len(candidates):
return None 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', log.debug('Picking a 2nd hop to measure %s from %d choices. is_exit=%s',
relay.nickname, len(candidates), is_exit) relay.nickname, len(candidates), is_exit)
for min_bw_factor in [2, 1.75, 1.5, 1.25, 1]: for min_bw_factor in [2, 1.75, 1.5, 1.25, 1]:
min_bw = relay.consensus_bandwidth * min_bw_factor 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 relaylist.py).
if min_bw < min_relay_bw:
min_bw = min_relay_bw
new_candidates = stem_utils.only_relays_with_bandwidth( new_candidates = stem_utils.only_relays_with_bandwidth(
cont, candidates, min_bw=min_bw) cont, candidates, min_bw=min_bw)
if len(new_candidates) > 0: if len(new_candidates) > 0:
......
...@@ -278,6 +278,9 @@ class RelayList: ...@@ -278,6 +278,9 @@ class RelayList:
[], MAX_RECENT_PRIORITY_RELAY_COUNT, state, [], MAX_RECENT_PRIORITY_RELAY_COUNT, state,
"recent_measurement_attempt" "recent_measurement_attempt"
) )
# Start with 0 for the min bw for our second hops
self._exit_min_bw = 0
self._non_exit_min_bw = 0
self._refresh() self._refresh()
def _need_refresh(self): def _need_refresh(self):
...@@ -431,6 +434,10 @@ class RelayList: ...@@ -431,6 +434,10 @@ class RelayList:
int(self._measurements_period / 24 / 60 / 60), int(self._measurements_period / 24 / 60 / 60),
self.recent_consensus_count) self.recent_consensus_count)
# Calculate minimum bandwidth value for 2nd hop after we refreshed
# our available relays.
self._calculate_min_bw_second_hop()
@property @property
def recent_consensus_count(self): def recent_consensus_count(self):
"""Number of times a new consensus was obtained.""" """Number of times a new consensus was obtained."""
...@@ -455,3 +462,31 @@ class RelayList: ...@@ -455,3 +462,31 @@ class RelayList:
@property @property
def recent_measurement_attempt_count(self): def recent_measurement_attempt_count(self):
return len(self._recent_measurement_attempt) 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 ...@@ -3,6 +3,7 @@ import pytest
import os.path import os.path
from unittest import mock from unittest import mock
from freezegun import freeze_time
from stem import descriptor from stem import descriptor
from sbws import settings from sbws import settings
...@@ -120,7 +121,8 @@ def router_status(server_descriptor, router_statuses): ...@@ -120,7 +121,8 @@ def router_status(server_descriptor, router_statuses):
@pytest.fixture(scope='function') @pytest.fixture(scope='function')
def relay_list(args, conf, controller): def relay_list(args, conf, controller):
"""Returns a RelayList containing the Relays in the 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)
@pytest.fixture(scope='function') @pytest.fixture(scope='function')
......
...@@ -18,6 +18,8 @@ def test_init_relays( ...@@ -18,6 +18,8 @@ def test_init_relays(
Test `init_relays` when creating the RelayList the first time and when a Test `init_relays` when creating the RelayList the first time and when a
new consensus is received. new consensus is received.
Test that the number of consesus timesamps and relays is correct. 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']) state = State(conf['paths']['state_fpath'])
# There is no need to mock datetime to update the consensus, since the # There is no need to mock datetime to update the consensus, since the
...@@ -31,6 +33,9 @@ def test_init_relays( ...@@ -31,6 +33,9 @@ def test_init_relays(
# The actual number of relays in the consensus # The actual number of relays in the consensus
assert len(relay_list._relays) == 6433 assert len(relay_list._relays) == 6433
fps = {r.fingerprint for r in relay_list._relays} 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 # One hour later there is a new consensus
relay_list._controller = controller_1h_later relay_list._controller = controller_1h_later
...@@ -44,6 +49,9 @@ def test_init_relays( ...@@ -44,6 +49,9 @@ def test_init_relays(
fps_1h_later = {r.fingerprint for r in relay_list._relays} fps_1h_later = {r.fingerprint for r in relay_list._relays}
added_fps = fps_1h_later.difference(fps) added_fps = fps_1h_later.difference(fps)
assert 6505 == 6433 + len(added_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. # Five days later plus 1 second.
# The first consensus timestamp will get removed. # The first consensus timestamp will get removed.
...@@ -62,6 +70,9 @@ def test_init_relays( ...@@ -62,6 +70,9 @@ def test_init_relays(
# The number of relays will be the number of relays in the cosensus plus # The number of relays will be the number of relays in the cosensus plus
# the added ones minus the removed ones. # the added ones minus the removed ones.
assert 6925 == 6505 + len(added_fps) - len(removed_fps) 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): def test_increment_recent_measurement_attempt(args, conf, controller):
...@@ -71,9 +82,8 @@ 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. It also tests that the state file is updated correctly.
""" """
state = State(conf['paths']['state_fpath']) state = State(conf['paths']['state_fpath'])
# For this test it does not matter that the consensus timestamps or relays with freeze_time("2020-02-29 10:00:00"):
# are not correct. relay_list = RelayList(args, conf, controller=controller, state=state)
relay_list = RelayList(args, conf, controller=controller, state=state)
# The initial count is 0 and the state does not have that key. # The initial count is 0 and the state does not have that key.
assert 0 == relay_list.recent_measurement_attempt_count assert 0 == relay_list.recent_measurement_attempt_count
assert not state.get("recent_measurement_attempt", None) 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