Prepare v0.6.7-alpha discovery release
This commit is contained in:
parent
ef30369865
commit
96f628814e
7 changed files with 75 additions and 12 deletions
|
|
@ -79,6 +79,9 @@ See [`docs/API.md`](docs/API.md) for the versioned HTTP contract.
|
||||||
|
|
||||||
## Repository relationship
|
## Repository relationship
|
||||||
|
|
||||||
This is deliberately a separate repository from the Godot game. The game and service can version,
|
This is deliberately a separate repository from the Godot game so the service can deploy and roll
|
||||||
deploy, and roll back independently. The discovery API version is independent from NETfishing's
|
back independently. Production tags follow the coordinated NETfishing release train: a
|
||||||
save schema and gameplay network protocol.
|
`vX.Y.Z-alpha` tag identifies the discovery snapshot tested for the same game and dedicated-server
|
||||||
|
release. The versioned HTTP contract remains independent from NETfishing's save schema and gameplay
|
||||||
|
network protocol. Each tagged deployment accepts room advertisements only from its matching game
|
||||||
|
release so incompatible rooms are never presented as publicly available.
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ All request and response bodies use `application/json`. Production clients must
|
||||||
|
|
||||||
Returns service status, package version, configured build revision, and the number of active,
|
Returns service status, package version, configured build revision, and the number of active,
|
||||||
unexpired room leases. Deployments should set `NETFISHING_DISCOVERY_BUILD_REVISION` to the exact
|
unexpired room leases. Deployments should set `NETFISHING_DISCOVERY_BUILD_REVISION` to the exact
|
||||||
Git commit they are running.
|
Git commit they are running. The package version follows the coordinated NETfishing release tag.
|
||||||
|
|
||||||
## `GET /v1/rooms`
|
## `GET /v1/rooms`
|
||||||
|
|
||||||
|
|
@ -32,7 +32,7 @@ trusting an address supplied by the game client.
|
||||||
"port": 7777,
|
"port": 7777,
|
||||||
"current_players": 0,
|
"current_players": 0,
|
||||||
"max_players": 8,
|
"max_players": 8,
|
||||||
"game_version": "0.6.4-alpha",
|
"game_version": "0.6.7-alpha",
|
||||||
"protocol_version": 3
|
"protocol_version": 3
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
@ -47,6 +47,10 @@ excluded from public listings until the service observes that endpoint.
|
||||||
The service applies configured global and per-observed-address active-room limits. Exceeding one
|
The service applies configured global and per-observed-address active-room limits. Exceeding one
|
||||||
returns `429 room_limit`; expired leases stop counting automatically.
|
returns `429 room_limit`; expired leases stop counting automatically.
|
||||||
|
|
||||||
|
The discovery release accepts room advertisements only from the matching NETfishing game release.
|
||||||
|
A mismatched `game_version` returns `409 game_version_mismatch` with `required_game_version`; the
|
||||||
|
room is not created or updated.
|
||||||
|
|
||||||
## `PUT /v1/rooms/{room_id}`
|
## `PUT /v1/rooms/{room_id}`
|
||||||
|
|
||||||
Renews and updates a room lease. Send the complete room payload and:
|
Renews and updates a room lease. Send the complete room payload and:
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
"""NETfishing discovery service."""
|
"""NETfishing discovery service."""
|
||||||
|
|
||||||
__version__ = "0.2.0"
|
__version__ = "0.6.7-alpha"
|
||||||
|
|
|
||||||
|
|
@ -184,6 +184,8 @@ class DiscoveryRequestHandler(BaseHTTPRequestHandler):
|
||||||
payload = self._read_json_object()
|
payload = self._read_json_object()
|
||||||
if payload is None:
|
if payload is None:
|
||||||
return
|
return
|
||||||
|
if self._reject_incompatible_game_version(payload):
|
||||||
|
return
|
||||||
try:
|
try:
|
||||||
room, token, verification_token = self.server.registry.create(
|
room, token, verification_token = self.server.registry.create(
|
||||||
self._client_address(), payload
|
self._client_address(), payload
|
||||||
|
|
@ -211,6 +213,8 @@ class DiscoveryRequestHandler(BaseHTTPRequestHandler):
|
||||||
payload = self._read_json_object()
|
payload = self._read_json_object()
|
||||||
if payload is None:
|
if payload is None:
|
||||||
return
|
return
|
||||||
|
if self._reject_incompatible_game_version(payload):
|
||||||
|
return
|
||||||
try:
|
try:
|
||||||
room = self.server.registry.update(
|
room = self.server.registry.update(
|
||||||
room_id,
|
room_id,
|
||||||
|
|
@ -300,6 +304,22 @@ class DiscoveryRequestHandler(BaseHTTPRequestHandler):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return peer_text
|
return peer_text
|
||||||
|
|
||||||
|
def _reject_incompatible_game_version(self, payload: dict[str, Any]) -> bool:
|
||||||
|
game_version = payload.get("game_version")
|
||||||
|
if not isinstance(game_version, str) or game_version.strip() == __version__:
|
||||||
|
return False
|
||||||
|
self._send_error(
|
||||||
|
HTTPStatus.CONFLICT,
|
||||||
|
"game_version_mismatch",
|
||||||
|
(
|
||||||
|
f"This discovery server only lists NETfishing {__version__} rooms. "
|
||||||
|
"Your room will not be listed until the game and discovery server "
|
||||||
|
"use the same version."
|
||||||
|
),
|
||||||
|
{"required_game_version": __version__},
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
def _room_id_from_path(self) -> str | None:
|
def _room_id_from_path(self) -> str | None:
|
||||||
path = urlsplit(self.path).path
|
path = urlsplit(self.path).path
|
||||||
prefix = "/v1/rooms/"
|
prefix = "/v1/rooms/"
|
||||||
|
|
@ -341,8 +361,17 @@ class DiscoveryRequestHandler(BaseHTTPRequestHandler):
|
||||||
raise ValueError
|
raise ValueError
|
||||||
return values[0]
|
return values[0]
|
||||||
|
|
||||||
def _send_error(self, status: HTTPStatus, code: str, message: str) -> None:
|
def _send_error(
|
||||||
self._send_json(status, {"error": {"code": code, "message": message}})
|
self,
|
||||||
|
status: HTTPStatus,
|
||||||
|
code: str,
|
||||||
|
message: str,
|
||||||
|
details: dict[str, Any] | None = None,
|
||||||
|
) -> None:
|
||||||
|
error: dict[str, Any] = {"code": code, "message": message}
|
||||||
|
if details is not None:
|
||||||
|
error.update(details)
|
||||||
|
self._send_json(status, {"error": error})
|
||||||
|
|
||||||
def _send_json(self, status: HTTPStatus, payload: dict[str, Any]) -> None:
|
def _send_json(self, status: HTTPStatus, payload: dict[str, Any]) -> None:
|
||||||
body = json.dumps(payload, ensure_ascii=False, separators=(",", ":")).encode("utf-8")
|
body = json.dumps(payload, ensure_ascii=False, separators=(",", ":")).encode("utf-8")
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "netfishing-discovery-server"
|
name = "netfishing-discovery-server"
|
||||||
version = "0.2.0"
|
version = "0.6.7-alpha"
|
||||||
description = "Ephemeral public-room directory for NETfishing"
|
description = "Ephemeral public-room directory for NETfishing"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
dependencies = []
|
dependencies = []
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ VALID_ROOM = {
|
||||||
"port": 7777,
|
"port": 7777,
|
||||||
"current_players": 1,
|
"current_players": 1,
|
||||||
"max_players": 8,
|
"max_players": 8,
|
||||||
"game_version": "0.6.4-alpha",
|
"game_version": "0.6.7-alpha",
|
||||||
"protocol_version": 3,
|
"protocol_version": 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -91,6 +91,19 @@ class DiscoveryHTTPTests(unittest.TestCase):
|
||||||
listed_rooms = listing["rooms"]
|
listed_rooms = listing["rooms"]
|
||||||
self.assertTrue(any(candidate["room_id"] == room_id for candidate in listed_rooms))
|
self.assertTrue(any(candidate["room_id"] == room_id for candidate in listed_rooms))
|
||||||
|
|
||||||
|
status, updated = self.request(
|
||||||
|
"PUT",
|
||||||
|
f"/v1/rooms/{room_id}",
|
||||||
|
dict(VALID_ROOM, game_version="0.6.6-alpha"),
|
||||||
|
str(token),
|
||||||
|
)
|
||||||
|
self.assertEqual(status, 409)
|
||||||
|
assert updated is not None
|
||||||
|
self.assertEqual(updated["error"]["code"], "game_version_mismatch")
|
||||||
|
self.assertEqual(
|
||||||
|
updated["error"]["required_game_version"], "0.6.7-alpha"
|
||||||
|
)
|
||||||
|
|
||||||
status, updated = self.request(
|
status, updated = self.request(
|
||||||
"PUT",
|
"PUT",
|
||||||
f"/v1/rooms/{room_id}",
|
f"/v1/rooms/{room_id}",
|
||||||
|
|
@ -110,6 +123,20 @@ class DiscoveryHTTPTests(unittest.TestCase):
|
||||||
assert body is not None
|
assert body is not None
|
||||||
self.assertEqual(body["error"]["code"], "invalid_room")
|
self.assertEqual(body["error"]["code"], "invalid_room")
|
||||||
|
|
||||||
|
def test_outdated_game_cannot_list_a_room(self) -> None:
|
||||||
|
status, body = self.request(
|
||||||
|
"POST",
|
||||||
|
"/v1/rooms",
|
||||||
|
dict(VALID_ROOM, game_version="0.6.6-alpha"),
|
||||||
|
)
|
||||||
|
self.assertEqual(status, 409)
|
||||||
|
assert body is not None
|
||||||
|
error = body["error"]
|
||||||
|
assert isinstance(error, dict)
|
||||||
|
self.assertEqual(error["code"], "game_version_mismatch")
|
||||||
|
self.assertEqual(error["required_game_version"], "0.6.7-alpha")
|
||||||
|
self.assertIn("will not be listed", str(error["message"]))
|
||||||
|
|
||||||
def test_empty_dedicated_room_can_be_listed(self) -> None:
|
def test_empty_dedicated_room_can_be_listed(self) -> None:
|
||||||
status, created = self.request(
|
status, created = self.request(
|
||||||
"POST", "/v1/rooms", dict(VALID_ROOM, current_players=0)
|
"POST", "/v1/rooms", dict(VALID_ROOM, current_players=0)
|
||||||
|
|
@ -173,7 +200,7 @@ class DiscoveryHTTPTests(unittest.TestCase):
|
||||||
self.assertEqual(status, 200)
|
self.assertEqual(status, 200)
|
||||||
assert body is not None
|
assert body is not None
|
||||||
self.assertEqual(body["status"], "ok")
|
self.assertEqual(body["status"], "ok")
|
||||||
self.assertEqual(body["version"], "0.2.0")
|
self.assertEqual(body["version"], "0.6.7-alpha")
|
||||||
self.assertEqual(body["build_revision"], "test-build")
|
self.assertEqual(body["build_revision"], "test-build")
|
||||||
|
|
||||||
def test_udp_rendezvous_does_not_spawn_per_packet_threads(self) -> None:
|
def test_udp_rendezvous_does_not_spawn_per_packet_threads(self) -> None:
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ VALID_ROOM = {
|
||||||
"port": 7777,
|
"port": 7777,
|
||||||
"current_players": 1,
|
"current_players": 1,
|
||||||
"max_players": 8,
|
"max_players": 8,
|
||||||
"game_version": "0.6.4-alpha",
|
"game_version": "0.6.7-alpha",
|
||||||
"protocol_version": 3,
|
"protocol_version": 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue