Skip to content

Commit

Permalink
Merge pull request #8 from cmu-sei/test
Browse files Browse the repository at this point in the history
Fix snapshot loading from database & playtest fixes
  • Loading branch information
sei-mkaar authored Nov 10, 2022
2 parents 69f6c41 + bc2f9c7 commit a89bf33
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 16 deletions.
37 changes: 30 additions & 7 deletions gamebrain/admin/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ async def assign_headless(cls, team_id: TeamID):


class ConsoleUrl(BaseModel):
Id: str
Url: str
Name: str
id: str
url: str
name: str


class DeployResponse(BaseModel):
Expand Down Expand Up @@ -94,9 +94,9 @@ def console_urls_from_vm_data(
console_urls.append(
ConsoleUrl(
**{
"Id": vm["id"],
"Url": construct_vm_url(gamespace_id, vm["name"]),
"Name": vm["name"],
"id": vm["id"],
"url": construct_vm_url(gamespace_id, vm["name"]),
"name": vm["name"],
}
)
)
Expand Down Expand Up @@ -230,7 +230,7 @@ async def deploy(
team_id, [console_url.dict() for console_url in console_urls]
)
await GameStateManager.update_team_urls(
team_id, {vm.Name: vm.Url for vm in console_urls}
team_id, {vm.name: vm.url for vm in console_urls}
)
logging.info(
f"Registered gamespace {gamespace_id} for team {team_id}, "
Expand Down Expand Up @@ -299,3 +299,26 @@ async def get_team_progress(team_id: TeamID) -> MissionProgressResponse:
except NonExistentTeam:
raise HTTPException(status_code=404, detail="Team not found.")
return MissionProgressResponse(__root__=status)


class UpdateConsoleUrlsPostData(BaseModel):
__root__: list[ConsoleUrl]


@admin_router.post("/update_console_urls/{team_id}")
async def update_console_urls(team_id: TeamID, post_data: UpdateConsoleUrlsPostData):
async with TeamLocks(team_id):
team_data = await get_team_from_db(team_id)
if not team_data:
logging.error(f"update_console_urls Team {team_id} does not exist.")
raise HTTPException(status_code=400, detail="Team does not exist.")

console_urls = post_data.__root__
logging.info(f"Got a console URL update for team {team_id}: {console_urls}")

await store_virtual_machines(
team_id, [console_url.dict() for console_url in console_urls]
)
await GameStateManager.update_team_urls(
team_id, {vm.name: vm.url for vm in console_urls}
)
16 changes: 14 additions & 2 deletions gamebrain/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@
WebSocketDisconnect,
Request,
)
from fastapi.exception_handlers import http_exception_handler
from fastapi.exceptions import RequestValidationError
from fastapi.exception_handlers import (
http_exception_handler,
request_validation_exception_handler,
)
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from pydantic import BaseModel
import yappi
Expand Down Expand Up @@ -56,11 +60,19 @@ def _profiling_output():


@APP.exception_handler(HTTPException)
async def debug_exception_handler(request, exc):
async def debug_exception_handler(request: Request, exc: HTTPException):
logging.error(request.headers)
return await http_exception_handler(request, exc)


@APP.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
logging.error(
f"Got invalid request headers: {request.headers} and body {str(request)}"
)
return await request_validation_exception_handler(request, exc)


# unpriv_router = APIRouter(prefix="/unprivileged")
priv_router = APIRouter(prefix="/privileged")
gamestate_router = APIRouter(prefix="/gamestate")
Expand Down
14 changes: 14 additions & 0 deletions gamebrain/clients/gameboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,17 @@ async def create_challenge(game_id: str, team_id: str):
"points": get_settings().game.total_points,
},
)


async def mission_update(
team_id: str, mission_id: str, mission_name: str, points_scored: int
):
return await _gameboard_post(
"unity/mission-update",
{
"teamId": team_id,
"missionId": mission_id,
"missionName": mission_name,
"pointsScored": points_scored,
},
)
12 changes: 10 additions & 2 deletions gamebrain/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import httpx
import yaml
from pydantic import BaseModel, validator
from pydantic import BaseModel, validator, ValidationError

from .clients import gameboard, topomojo
from .dispatch import GamespaceStatusTask
Expand Down Expand Up @@ -183,7 +183,15 @@ async def init(cls):
"game.gamestate_test_mode setting is ON, constructing initial data from test constructor."
)
elif stored_cache := await db.get_cache_snapshot():
initial_cache = GameDataCacheSnapshot(**stored_cache)
stored_cache_dict = json.loads(stored_cache)
try:
initial_cache = GameDataCacheSnapshot(**stored_cache_dict)
except ValidationError as e:
logging.error(
"Tried to load game state cache and failed validation. Got the following: "
f"{json.dumps(stored_cache_dict, indent=2)}"
)
raise e
logging.info("Initializing game data cache from saved snapshot.")
else:
with open("initial_state.json") as f:
Expand Down
9 changes: 5 additions & 4 deletions gamebrain/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,11 @@ async def get_events(team_id: Optional[str] = None):

async def store_virtual_machines(team_id: str, vms: List[Dict]):
"""
vms: List of {"Id": str, "Url": str, "Name": str} dicts
vms: List of {"id": str, "url": str, "name": str} dicts
"""
vm_data = [
DBManager.VirtualMachine(
id=vm["Id"], team_id=team_id, url=vm["Url"], name=vm["Name"]
id=vm["id"], team_id=team_id, url=vm["url"], name=vm["name"]
)
for vm in vms
]
Expand Down Expand Up @@ -243,13 +243,14 @@ async def store_cache_snapshot(cache_snapshot: str):
await DBManager.merge_rows([snapshot])


async def get_cache_snapshot():
async def get_cache_snapshot() -> str | None:
try:
return (
db_row = (
await DBManager.get_rows(
DBManager.CacheSnapshot, DBManager.CacheSnapshot.id == 0
)
).pop()
return db_row["snapshot"]
except IndexError:
return None

Expand Down
39 changes: 38 additions & 1 deletion gamebrain/gamedata/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
GenericResponse,
ScanResponse,
)
from ..clients import topomojo
from ..clients import gameboard, topomojo

CommID = str
LocationID = str
Expand Down Expand Up @@ -426,6 +426,31 @@ def _complete_task_and_unlock_next(
)
return True
mission.complete = True
global_mission = cls._cache.mission_map.__root__.get(mission.missionID)
if not global_mission:
logging.error(
f"Team {team_id} had unlocked a mission with ID {mission.missionID}, but it does not "
"exist in the global data."
)
else:
total_points = cls._settings.game.total_points
mission_count = len(cls._cache.mission_map.__root__)
mission_points = total_points // mission_count
task = asyncio.create_task(
gameboard.mission_update(
team_id,
global_mission.missionID,
global_mission.title,
mission_points,
)
)
task.add_done_callback(
lambda _: logging.info(
f"Team {team_id} completed mission "
f"{global_mission.missionID} and was awarded {mission_points} points."
)
)

team_data.session.teamCodexCount = sum(
(
1 if mission.complete else 0
Expand Down Expand Up @@ -983,6 +1008,18 @@ async def jump(cls, team_id: TeamID, location_id: LocationID) -> GenericResponse
team_db_data = await get_team(team_id)
gamespace_id = team_db_data.get("gamespace_id")

total_points = cls._settings.game.total_points
mission_count = len(cls._cache.mission_map.__root__)
# Each mission gets total_points // mission_count,
# so the final goal should get whatever points remain.
final_goal_total_points = total_points - (
(total_points // mission_count) * (mission_count - 1)
)

await gameboard.mission_update(
team_id, "finalGoal", "Final Goal", final_goal_total_points
)

if not gamespace_id:
logging.error(
f"Team {team_id} tried to unlock the final destination, "
Expand Down

0 comments on commit a89bf33

Please sign in to comment.