Files
missions/_docs/02_document/diagrams/flows/flow_startup_migration.md
T
Oleksandr Bezdieniezhnykh 7025f4d075 refactor: enhance JWT authentication and CORS configuration
Updated JWT authentication to use configuration values instead of hardcoded secrets, improving security and flexibility. Enhanced CORS policy to conditionally allow origins based on configuration settings, with logging for permissive defaults. Updated README to reflect project renaming and clarify service context.
2026-05-14 19:48:25 +03:00

5.8 KiB

Flow F6 — Service startup + schema migration

One-shot per process start. Idempotent migrator (CREATE ... IF NOT EXISTS). Post-B9 the migrator additionally DROP TABLE IF EXISTS orthophotos / gps_corrections for fielded edge devices that previously ran the legacy schema.

Description

Program.cs builds the DI graph from environment, runs DatabaseMigrator.Migrate(db) once inside a startup scope, then starts serving HTTP. The migrator only owns the 4 tables this service is responsible for (vehicles, missions, waypoints, map_objects); the borrowed tables (media, annotations, detection) are migrated by their owner services in their own startups.

Preconditions

  • DATABASE_URL resolves (env or hardcoded dev fallback).
  • postgres-local is reachable.
  • The azaion database itself exists in PostgreSQL (created at provisioning time, NOT by this migrator).

Sequence Diagram

sequenceDiagram
    autonumber
    participant Docker as Docker / Watchtower
    participant Host as 07_host (Program.cs)
    participant Cfg as IConfiguration
    participant Identity as 05_identity
    participant DI as DI container
    participant Migrator as DatabaseMigrator
    participant DB as postgres-local

    Docker->>Host: ENTRYPOINT dotnet Azaion.Missions.dll
    Host->>Cfg: read DATABASE_URL → ConvertPostgresUrl → Npgsql connection string
    Host->>Cfg: read JWT_SECRET (env or hardcoded fallback)
    Host->>Identity: AddJwtAuth(jwtSecret) — DI registration only, no network
    Host->>DI: register controllers + middleware + scoped AppDataConnection + scoped services
    Host->>DI: build Host
    Host->>Migrator: scope.Resolve<AppDataConnection>(); Migrate(db)
    Migrator->>DB: CREATE TABLE IF NOT EXISTS vehicles (...)
    Migrator->>DB: CREATE TABLE IF NOT EXISTS missions (...)
    Migrator->>DB: CREATE TABLE IF NOT EXISTS waypoints (...)
    Migrator->>DB: CREATE TABLE IF NOT EXISTS map_objects (...)
    Migrator->>DB: CREATE INDEX IF NOT EXISTS ix_missions_vehicle_id, ix_waypoints_mission_id, ix_map_objects_mission_id
    Migrator->>DB: DROP TABLE IF EXISTS orthophotos
    Migrator->>DB: DROP TABLE IF EXISTS gps_corrections
    note right of Migrator: B9 one-shot. Idempotent on devices that already cleaned up.
    Migrator-->>Host: void
    Host->>Host: register ErrorHandlingMiddleware FIRST in pipeline
    Host->>Host: UseAuthentication / UseAuthorization
    Host->>Host: MapControllers + MapGet("/health") + UseSwagger
    Host->>Docker: app.Run() — listening on 0.0.0.0:8080

Flowchart

flowchart TD
    Start([Container start]) --> ReadCfg[Read DATABASE_URL + JWT_SECRET]
    ReadCfg --> RegDI[DI registrations: controllers, middleware, scoped DB + services]
    RegDI --> Build[Build Host]
    Build --> OpenScope[Open startup scope]
    OpenScope --> Migrate[Run DatabaseMigrator.Migrate]
    Migrate --> Create["CREATE TABLE IF NOT EXISTS x4 + indexes"]
    Create --> Drop["DROP TABLE IF EXISTS orthophotos, gps_corrections (B9)"]
    Drop --> Pipeline[Wire pipeline: error MW first, auth, controllers, /health, Swagger]
    Pipeline --> Run([app.Run on :8080])
    Migrate -. on failure .-> Crash([Process exits non-zero — Watchtower restarts])

Data Flow

Step From To Data Format
1 Environment / appsettings Program.cs DATABASE_URL, JWT_SECRET string
2 Program.cs DI container service registrations C# code
3 DatabaseMigrator postgres-local DDL statements (CREATE / INDEX / DROP) SQL
4 Program.cs OS / Docker bind to 0.0.0.0:8080 TCP listener

Error Scenarios

Error Where Detection Recovery
postgres-local unreachable Migrate step Npgsql IOException / SocketException Process exits non-zero. Watchtower restarts; flight-gate prevents restart mid-mission. Fix: ensure postgres-local healthcheck passes before missions starts (compose depends_on with condition: service_healthy)
azaion database missing Migrate step Npgsql 3D000 (invalid_catalog_name) Process exits. Operator must create the database — provisioning concern, not this service. Documented in ../../suite/_docs/00_top_level_architecture.md
DROP TABLE IF EXISTS orthophotos fails because table is locked by gps-denied B9 one-shot Lock timeout or 55006 Process exits, Watchtower restarts in a few seconds. Out-of-band ordering: deploy gps-denied FIRST so it has its own copy of the schema before missions drops the legacy tables. Documented in B9 ticket
One CREATE TABLE succeeds, the next fails Mid-Migrate Npgsql exception on later statement Process exits. Each statement is individually idempotent (IF NOT EXISTS) so the next startup retries safely from the start. No partial-migration cleanup needed
Wrong PostgreSQL version (e.g., < 13) Migrate step Specific syntax errors in newer features Process exits. Suite-supported version is PG 16+; older devices need a Postgres upgrade
DATABASE_URL malformed (e.g. user password contains @) ConvertPostgresUrl parse failure / silent mis-parse ConvertPostgresUrl does NOT URL-decode user/password — caveat for credentials with @, :, /, %. 07_host Caveats. Mitigation: avoid those chars in passwords, OR pass a raw Npgsql key=value string instead of a URL

Performance Expectations

Metric Target Notes
Cold start total time <2 seconds typical on Jetson Orin Migrator runs ~10 DDL statements; all are no-ops on a steady-state device
Cold start with legacy GPS-Denied tables present +1 second First-time-on-device B9 DROP adds two DDL statements
Crash recovery (Watchtower restart) ~10 seconds Container restart latency dominates