Skip to content

Commit b295c15

Browse files
authored
test: add tokenserver util tests (#2195)
test: add tokenserver util tests
1 parent e32579f commit b295c15

10 files changed

Lines changed: 62 additions & 18 deletions

.github/workflows/main-workflow.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1030,7 +1030,6 @@ jobs:
10301030
tags: app:build
10311031
build-args: |
10321032
SYNCSTORAGE_DATABASE_BACKEND=spanner
1033-
MYSQLCLIENT_PKG=libmysqlclient-dev
10341033
outputs: type=docker,dest=/tmp/spanner-image.tar
10351034
cache-from: type=gha
10361035
cache-to: type=gha,mode=max

Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ RUN apt-get -q update && \
119119
apt-get install -y --no-install-recommends gnupg ca-certificates wget && \
120120
echo "deb https://repo.mysql.com/apt/debian/ bookworm mysql-8.0" >> /etc/apt/sources.list && \
121121
# Fetch and install the MySQL public key
122+
# Key ID A8D3785C from https://dev.mysql.com/doc/refman/8.0/en/checking-gpg-signature.html
122123
gpg --batch --keyserver hkp://keyserver.ubuntu.com --recv-keys A8D3785C && \
123124
gpg --batch --armor --export A8D3785C | tee /etc/apt/trusted.gpg.d/mysql.asc && \
124125
apt-get -q update ; \
@@ -128,7 +129,7 @@ RUN apt-get -q update && \
128129
# The python3-cryptography debian package installs version 2.6.1, but we
129130
# we want to use the version specified in requirements.txt. To do this,
130131
# we have to remove the python3-cryptography package here.
131-
apt-get -q remove -y python3-cryptography 2>/dev/null || true && \
132+
(apt-get -q remove -y python3-cryptography 2>/dev/null || true) && \
132133
apt-get -q autoremove -y && \
133134
rm -rf /var/lib/apt/lists/* && \
134135
python3 --version

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ POSTGRES_INT_JUNIT_XML := $(TEST_RESULTS_DIR)/$(TEST_FILE_PREFIX)postgres_integr
4646
POSTGRES_NO_JWK_INT_JUNIT_XML := $(TEST_RESULTS_DIR)/$(TEST_FILE_PREFIX)postgres_no_oauth_integration__results.xml
4747
MYSQL_INT_JUNIT_XML := $(TEST_RESULTS_DIR)/$(TEST_FILE_PREFIX)mysql_integration__results.xml
4848
MYSQL_NO_JWK_INT_JUNIT_XML := $(TEST_RESULTS_DIR)/$(TEST_FILE_PREFIX)mysql_no_oauth_integration__results.xml
49+
TOKENSERVER_UTILS_JUNIT_XML := $(TEST_RESULTS_DIR)/$(TEST_FILE_PREFIX)tokenserver_utils__results.xml
4950

5051
LOCAL_INTEGRATION_JUNIT_XML := $(TEST_RESULTS_DIR)/$(TEST_FILE_PREFIX)local_integration__results.xml
5152
SYNC_SYNCSTORAGE__DATABASE_URL ?= mysql://sample_user:sample_password@localhost/syncstorage_rs
@@ -113,6 +114,7 @@ docker_run_mysql_e2e_tests:
113114
--exit-code-from e2e-tests \
114115
--abort-on-container-exit || exit_code=$$?
115116
docker cp mysql-e2e-tests:/mysql_integration_results.xml ${MYSQL_INT_JUNIT_XML}
117+
docker cp mysql-e2e-tests:/tokenserver_utils_results.xml ${TOKENSERVER_UTILS_JUNIT_XML}
116118
docker compose \
117119
-f docker/docker-compose.mysql.yaml \
118120
-f docker/docker-compose.e2e.mysql.yaml \
@@ -146,6 +148,7 @@ docker_run_postgres_e2e_tests:
146148
--exit-code-from e2e-tests \
147149
--abort-on-container-exit || exit_code=$$?
148150
docker cp postgres-e2e-tests:/postgres_integration_results.xml ${POSTGRES_INT_JUNIT_XML}
151+
docker cp postgres-e2e-tests:/tokenserver_utils_results.xml ${TOKENSERVER_UTILS_JUNIT_XML}
149152
docker compose \
150153
-f docker/docker-compose.postgres.yaml \
151154
-f docker/docker-compose.e2e.postgres.yaml \
@@ -179,6 +182,7 @@ docker_run_spanner_e2e_tests:
179182
--exit-code-from e2e-tests \
180183
--abort-on-container-exit || exit_code=$$?
181184
docker cp spanner-e2e-tests:/spanner_integration_results.xml ${SPANNER_INT_JUNIT_XML}
185+
docker cp spanner-e2e-tests:/tokenserver_utils_results.xml ${TOKENSERVER_UTILS_JUNIT_XML}
182186
docker compose \
183187
-f docker/docker-compose.spanner.yaml \
184188
-f docker/docker-compose.e2e.spanner.yaml \

docker/docker-compose.e2e.jwk-cache.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@ services:
1515
- -c
1616
- >-
1717
PYTHONPATH=/app
18-
pytest /app/tools/integration_tests/
18+
pytest /app/tools/integration_tests/ /app/tools/tokenserver/
1919
--junit-xml=/${RESULTS_FILENAME}

docker/docker-compose.e2e.mysql.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,5 @@ services:
3232
- -c
3333
- >-
3434
PYTHONPATH=/app
35-
pytest /app/tools/integration_tests/
35+
pytest /app/tools/integration_tests/ /app/tools/tokenserver/
3636
--junit-xml=/${RESULTS_FILENAME}

docker/docker-compose.e2e.postgres.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ services:
3030
entrypoint:
3131
- /bin/sh
3232
- -c
33-
- >-
34-
PYTHONPATH=/app
35-
pytest /app/tools/integration_tests/
33+
- >-
34+
PYTHONPATH=/app
35+
pytest /app/tools/integration_tests/ /app/tools/tokenserver/
3636
--junit-xml=/${RESULTS_FILENAME}

docker/docker-compose.e2e.spanner.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,5 @@ services:
3131
- -c
3232
- >-
3333
PYTHONPATH=/app
34-
pytest /app/tools/integration_tests/
34+
pytest /app/tools/integration_tests/ /app/tools/tokenserver/
3535
--junit-xml=/${RESULTS_FILENAME}

tools/tokenserver/database.py

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,9 @@
180180
""")
181181

182182

183-
_GET_BEST_NODE = sqltext("""\
183+
# MySQL: log(0) returns NULL, and NULLs sort first with ASC — zero-load
184+
# nodes naturally win. Original query unchanged.
185+
_GET_BEST_NODE_MYSQL = sqltext("""\
184186
select
185187
id, node
186188
from
@@ -196,6 +198,26 @@
196198
limit 1
197199
""")
198200

201+
# PostgreSQL: ln() is the natural log equivalent of MySQL's log(). NULLIF
202+
# converts zero to NULL to avoid InvalidArgumentForLogarithm. NULLS FIRST
203+
# replicates MySQL's default NULL-first ASC sort order, ensuring zero-load
204+
# nodes are always preferred.
205+
_GET_BEST_NODE_POSTGRES = sqltext("""\
206+
select
207+
id, node
208+
from
209+
nodes
210+
where
211+
service = :service
212+
and available > 0
213+
and capacity > current_load
214+
and downed = 0
215+
and backoff = 0
216+
order by
217+
ln(NULLIF(current_load, 0)) / ln(capacity) NULLS FIRST
218+
limit 1
219+
""")
220+
199221

200222
_RELEASE_NODE_CAPACITY = sqltext("""\
201223
update
@@ -616,11 +638,14 @@ def add_service(self, service_name, pattern, **kwds):
616638
pattern=pattern,
617639
**kwds,
618640
)
619-
res.close()
620641
if self.db_mode == "postgresql":
621-
return res.fetchone()[0]
642+
row = res.fetchone()[0]
643+
res.close()
644+
return row
622645
else:
623-
return res.lastrowid
646+
lastrowid = res.lastrowid
647+
res.close()
648+
return lastrowid
624649

625650
def add_node(self, node, capacity, **kwds):
626651
"""Add definition for a new node."""
@@ -653,8 +678,10 @@ def add_node(self, node, capacity, **kwds):
653678
capacity=capacity,
654679
available=available,
655680
current_load=kwds.get("current_load", 0),
656-
downed=kwds.get("downed", 0),
657-
backoff=kwds.get("backoff", 0),
681+
# Cast to int: optparse action="store_true" produces Python bools,
682+
# which postgres rejects for INTEGER columns (MySQL coerces silently).
683+
downed=int(kwds.get("downed", 0)),
684+
backoff=int(kwds.get("backoff", 0)),
658685
)
659686
res.close()
660687

@@ -676,6 +703,11 @@ def update_node(self, node, **kwds):
676703
query += """
677704
where service = :service and node = :node
678705
"""
706+
# Cast boolean fields to int: Python bools are rejected by postgres
707+
# INTEGER columns. MySQL coerces silently; postgres does not.
708+
for field in ("downed", "backoff"):
709+
if field in values:
710+
values[field] = int(values[field])
679711
values["service"] = self._get_service_id(SERVICE_NAME)
680712
values["node"] = node
681713
if kwds:
@@ -747,8 +779,16 @@ def get_best_node(self):
747779
# capacity. This loop allows a maximum of five retries before
748780
# bailing out.
749781
for _ in range(5):
782+
# Select the appropriate query variant — postgres requires
783+
# explicit NULL handling for log(0) and NULL sort order that
784+
# MySQL handles implicitly.
785+
best_node_query = (
786+
_GET_BEST_NODE_POSTGRES
787+
if self.db_mode == "postgresql"
788+
else _GET_BEST_NODE_MYSQL
789+
)
750790
res = self._execute_sql(
751-
_GET_BEST_NODE, service=self._get_service_id(SERVICE_NAME)
791+
best_node_query, service=self._get_service_id(SERVICE_NAME)
752792
)
753793
row = res.fetchone()
754794
res.close()

tools/tokenserver/test_process_account_events.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def tearDown(self):
4545
testing.tearDown()
4646

4747
cursor = self.database._execute_sql("DELETE FROM users")
48-
cursor.close
48+
cursor.close()
4949

5050
cursor = self.database._execute_sql("DELETE FROM nodes")
5151
cursor.close()

tools/tokenserver/update_node.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,9 @@ def main(args=None):
8383
if opts.current_load is not None:
8484
kwds["current_load"] = opts.current_load
8585
if opts.backoff is not None:
86-
kwds["backoff"] = opts.backoff
86+
kwds["backoff"] = int(opts.backoff)
8787
if opts.downed is not None:
88-
kwds["downed"] = opts.downed
88+
kwds["downed"] = int(opts.downed)
8989

9090
update_node(node_name, **kwds)
9191
return 0

0 commit comments

Comments
 (0)