Skip to content

Commit 4fadd57

Browse files
committed
Rework / simplify handling of external IP update
1 parent 7b534de commit 4fadd57

File tree

4 files changed

+96
-30
lines changed

4 files changed

+96
-30
lines changed

dispatcher/backend/src/common/constants.py

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,6 @@
33

44
from common.enum import SchedulePeriodicity
55

6-
7-
def refreshable_constant(fn):
8-
"""Refreshable constants helper for those we have interest to live update"""
9-
return fn
10-
11-
126
OPENSSL_BIN = os.getenv("OPENSSL_BIN", "/usr/bin/openssl")
137
MESSAGE_VALIDITY = 60 # number of seconds before a message expire
148

@@ -72,11 +66,7 @@ def refreshable_constant(fn):
7266
# using the following, it is possible to automate
7367
# the update of a whitelist of workers IPs on Wasabi (S3 provider)
7468
# enable this feature (default is off)
75-
# Nota: this is a refreshable constant so that it can be dynamically updated
76-
# (including in tests)
77-
USES_WORKERS_IPS_WHITELIST = refreshable_constant(
78-
lambda: bool(os.getenv("USES_WORKERS_IPS_WHITELIST", ""))
79-
)
69+
USES_WORKERS_IPS_WHITELIST = bool(os.getenv("USES_WORKERS_IPS_WHITELIST"))
8070
MAX_WORKER_IP_CHANGES_PER_DAY = 4
8171
# wasabi URL with credentials to update policy
8272
WASABI_URL = os.getenv("WASABI_URL", "")

dispatcher/backend/src/common/external.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929

3030
def update_workers_whitelist(session: so.Session):
3131
"""update whitelist of workers on external services"""
32-
IpUpdater.update_fn(build_workers_whitelist(session=session))
32+
ExternalIpUpdater.update_fn(build_workers_whitelist(session=session))
3333

3434

3535
def build_workers_whitelist(session: so.Session) -> typing.List[str]:
@@ -149,7 +149,7 @@ def get_statement():
149149
)
150150

151151

152-
class IpUpdater:
152+
class ExternalIpUpdater:
153153
update_fn = update_wasabi_whitelist
154154

155155

dispatcher/backend/src/routes/requested_tasks/requested_task.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,8 @@
99
from marshmallow import ValidationError
1010

1111
import db.models as dbm
12-
from common import WorkersIpChangesCounts, getnow
13-
from common.constants import (
14-
ENABLED_SCHEDULER,
15-
MAX_WORKER_IP_CHANGES_PER_DAY,
16-
USES_WORKERS_IPS_WHITELIST,
17-
)
12+
from common import WorkersIpChangesCounts, constants, getnow
13+
from common.constants import ENABLED_SCHEDULER, MAX_WORKER_IP_CHANGES_PER_DAY
1814
from common.external import update_workers_whitelist
1915
from common.schemas.orms import RequestedTaskFullSchema, RequestedTaskLightSchema
2016
from common.schemas.parameters import (
@@ -25,7 +21,7 @@
2521
)
2622
from common.utils import task_event_handler
2723
from db import count_from_stmt, dbsession, dbsession_manual
28-
from errors.http import InvalidRequestJSON, TaskNotFound, WorkerNotFound, HTTPBase
24+
from errors.http import HTTPBase, InvalidRequestJSON, TaskNotFound, WorkerNotFound
2925
from routes import auth_info_if_supplied, authenticate, require_perm, url_uuid
3026
from routes.base import BaseRoute
3127
from routes.errors import NotFound

dispatcher/backend/src/tests/integration/routes/workers/test_worker.py

Lines changed: 90 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import os
21
from typing import List
32

43
import pytest
54

6-
from common.external import IpUpdater, build_workers_whitelist
5+
from common import constants
6+
from common.external import ExternalIpUpdater, build_workers_whitelist
77

88

99
class TestWorkersCommon:
@@ -212,6 +212,8 @@ def test_checkin_another_user(
212212

213213

214214
class TestWorkerRequestedTasks:
215+
new_ip_address = "88.88.88.88"
216+
215217
def test_requested_task_worker_as_admin(self, client, access_token, worker):
216218
response = client.get(
217219
"/requested-tasks/worker",
@@ -238,18 +240,35 @@ def test_requested_task_worker_as_worker(self, client, make_access_token, worker
238240
)
239241
assert response.status_code == 200
240242

241-
new_ip_address = "88.88.88.88"
242-
243243
def custom_ip_update(self, ip_addresses: List):
244244
self.ip_updated = True
245245
assert TestWorkerRequestedTasks.new_ip_address in ip_addresses
246246

247+
def custom_failing_ip_update(self, ip_addresses: List):
248+
raise Exception()
249+
250+
@pytest.mark.parametrize(
251+
"prev_ip, new_ip, external_update_enabled, external_update_fails,"
252+
" external_update_called",
253+
[
254+
("77.77.77.77", "88.88.88.88", False, False, False), # ip update disabled
255+
("77.77.77.77", "77.77.77.77", True, False, False), # ip did not changed
256+
("77.77.77.77", "88.88.88.88", True, False, True), # ip should be updated
257+
("77.77.77.77", "88.88.88.88", True, True, False), # ip update fails
258+
],
259+
)
247260
def test_requested_task_worker_update_ip_whitelist(
248-
self, client, make_access_token, worker
261+
self,
262+
client,
263+
make_access_token,
264+
worker,
265+
prev_ip,
266+
new_ip,
267+
external_update_enabled,
268+
external_update_fails,
269+
external_update_called,
249270
):
250-
self.ip_updated = False
251-
IpUpdater.update_fn = self.custom_ip_update
252-
os.environ["USES_WORKERS_IPS_WHITELIST"] = "1"
271+
# call it once to set prev_ip
253272
response = client.get(
254273
"/requested-tasks/worker",
255274
query_string={
@@ -260,8 +279,69 @@ def test_requested_task_worker_update_ip_whitelist(
260279
},
261280
headers={
262281
"Authorization": make_access_token(worker["username"], "worker"),
263-
"X-Forwarded-For": TestWorkerRequestedTasks.new_ip_address,
282+
"X-Forwarded-For": prev_ip,
264283
},
265284
)
266285
assert response.status_code == 200
267-
assert self.ip_updated
286+
287+
# check prev_ip has been set
288+
response = client.get("/workers/")
289+
assert response.status_code == 200
290+
response_data = response.get_json()
291+
for item in response_data["items"]:
292+
if item["name"] != worker["name"]:
293+
continue
294+
assert item["last_ip"] == prev_ip
295+
296+
# setup custom ip updater to intercept Wasabi operations
297+
updater = IpUpdaterAndChecker(should_fail=external_update_fails)
298+
assert TestWorkerRequestedTasks.new_ip_address not in updater.ip_addresses
299+
ExternalIpUpdater.update_fn = updater.ip_update
300+
constants.USES_WORKERS_IPS_WHITELIST = external_update_enabled
301+
302+
# call it once to set next_ip
303+
response = client.get(
304+
"/requested-tasks/worker",
305+
query_string={
306+
"worker": worker["name"],
307+
"avail_cpu": 4,
308+
"avail_memory": 2048,
309+
"avail_disk": 4096,
310+
},
311+
headers={
312+
"Authorization": make_access_token(worker["username"], "worker"),
313+
"X-Forwarded-For": new_ip,
314+
},
315+
)
316+
if external_update_fails:
317+
assert response.status_code == 503
318+
else:
319+
assert response.status_code == 200
320+
assert updater.ips_updated == external_update_called
321+
if external_update_called:
322+
assert new_ip in updater.ip_addresses
323+
324+
# check new_ip has been set (even if ip update is disabled or has failed)
325+
response = client.get("/workers/")
326+
assert response.status_code == 200
327+
response_data = response.get_json()
328+
for item in response_data["items"]:
329+
if item["name"] != worker["name"]:
330+
continue
331+
assert item["last_ip"] == new_ip
332+
333+
334+
class IpUpdaterAndChecker:
335+
"""Helper class to intercept Wasabi operations and perform assertions"""
336+
337+
def __init__(self, should_fail: bool) -> None:
338+
self.ips_updated = False
339+
self.should_fail = should_fail
340+
self.ip_addresses = []
341+
342+
def ip_update(self, ip_addresses: List):
343+
if self.should_fail:
344+
raise Exception()
345+
else:
346+
self.ips_updated = True
347+
self.ip_addresses = ip_addresses

0 commit comments

Comments
 (0)