feat(coverage): write rasters to NFS-backed dir, serve via dedicated static plug
Coverage rasters were being written to priv/static/coverage on the pod's
ephemeral filesystem — fine on a single-pod dev box, broken under k8s
where pods share NFS at /data and a restart wipes computed predictions.
* New :coverage_storage_dir config (defaults to priv/static/coverage for
dev/test, "/data/coverage" in production).
* Raster.output_dir/absolute_raster_path read this config and resolve
{otp_app, rel} or absolute paths.
* Endpoint now mounts a dedicated Plug.Static at /coverage from that
same config — replicas all serve the same NFS-backed file regardless
of which one ran the compute.
* Drop "coverage" from static_paths/0 since the dedicated plug owns
that prefix now.
This commit is contained in:
parent
f266b4ed34
commit
4a8d8cb208
5 changed files with 39 additions and 5 deletions
|
|
@ -123,6 +123,11 @@ config :towerops, ToweropsWeb.Endpoint,
|
||||||
pubsub_server: Towerops.PubSub,
|
pubsub_server: Towerops.PubSub,
|
||||||
live_view: [signing_salt: "Uh1ABfdI"]
|
live_view: [signing_salt: "Uh1ABfdI"]
|
||||||
|
|
||||||
|
# Default coverage raster directory for dev/test. Production overrides this
|
||||||
|
# to "/data/coverage" (the shared NFS mount) in config/prod.exs so all
|
||||||
|
# replicas can serve the rasters and pod restarts don't lose them.
|
||||||
|
config :towerops, :coverage_storage_dir, {:towerops, "priv/static/coverage"}
|
||||||
|
|
||||||
# SNMP MIB directories for production (in Docker image)
|
# SNMP MIB directories for production (in Docker image)
|
||||||
# MIB files are included in the release at /app/priv/mibs
|
# MIB files are included in the release at /app/priv/mibs
|
||||||
# MibTranslator automatically expands these to include subdirectories
|
# MibTranslator automatically expands these to include subdirectories
|
||||||
|
|
|
||||||
|
|
@ -39,3 +39,8 @@ config :swoosh, local: false
|
||||||
# before starting your production server.
|
# before starting your production server.
|
||||||
config :towerops, ToweropsWeb.Endpoint, cache_static_manifest: "priv/static/cache_manifest.json"
|
config :towerops, ToweropsWeb.Endpoint, cache_static_manifest: "priv/static/cache_manifest.json"
|
||||||
# SSL/TLS is handled by Cloudflared proxy - no force_ssl needed
|
# SSL/TLS is handled by Cloudflared proxy - no force_ssl needed
|
||||||
|
|
||||||
|
# Coverage rasters live on the shared NFS mount so all replicas can serve
|
||||||
|
# them and a pod restart doesn't lose computed predictions. The
|
||||||
|
# deployment mounts the NFS share at /data.
|
||||||
|
config :towerops, :coverage_storage_dir, "/data/coverage"
|
||||||
|
|
|
||||||
|
|
@ -142,7 +142,15 @@ defmodule Towerops.Coverages.Raster do
|
||||||
defp inside_bbox?(_coverage, _lat, _lon), do: false
|
defp inside_bbox?(_coverage, _lat, _lon), do: false
|
||||||
|
|
||||||
defp absolute_raster_path(%Coverage{raster_path: rel}) do
|
defp absolute_raster_path(%Coverage{raster_path: rel}) do
|
||||||
Path.join(static_dir(), Path.relative_to(rel, "/"))
|
# rel looks like "/coverage/<org>/<id>/rssi*.tif"; strip the URL prefix
|
||||||
|
# and rejoin against the configured storage dir (which already corresponds
|
||||||
|
# to /coverage in the static plug).
|
||||||
|
suffix =
|
||||||
|
rel
|
||||||
|
|> Path.relative_to("/")
|
||||||
|
|> String.replace_prefix("coverage/", "")
|
||||||
|
|
||||||
|
Path.join(coverage_dir(), suffix)
|
||||||
end
|
end
|
||||||
|
|
||||||
# gdallocationinfo with -wgs84 -valonly prints just the pixel value or
|
# gdallocationinfo with -wgs84 -valonly prints just the pixel value or
|
||||||
|
|
@ -179,11 +187,19 @@ defmodule Towerops.Coverages.Raster do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp output_dir(%Coverage{organization_id: org_id, id: id}) do
|
defp output_dir(%Coverage{organization_id: org_id, id: id}) do
|
||||||
Path.join([static_dir(), "coverage", to_string(org_id), to_string(id)])
|
Path.join([coverage_dir(), to_string(org_id), to_string(id)])
|
||||||
end
|
end
|
||||||
|
|
||||||
defp static_dir do
|
# Resolves :coverage_storage_dir config to an absolute path.
|
||||||
Application.app_dir(:towerops, "priv/static")
|
#
|
||||||
|
# * `{:otp_app, "rel/path"}` → `Application.app_dir/2` (dev/test default
|
||||||
|
# so rasters live under priv/static and Mix releases pick them up).
|
||||||
|
# * `"/abs/path"` → used as-is (production points at the NFS mount).
|
||||||
|
defp coverage_dir do
|
||||||
|
case Application.get_env(:towerops, :coverage_storage_dir, {:towerops, "priv/static/coverage"}) do
|
||||||
|
{app, rel} when is_atom(app) and is_binary(rel) -> Application.app_dir(app, rel)
|
||||||
|
path when is_binary(path) -> path
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp relative_static_path(coverage, filename) do
|
defp relative_static_path(coverage, filename) do
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ defmodule ToweropsWeb do
|
||||||
those modules here.
|
those modules here.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def static_paths, do: ~w(assets fonts images vendor coverage favicon.ico robots.txt changelog.txt)
|
def static_paths, do: ~w(assets fonts images vendor favicon.ico robots.txt changelog.txt)
|
||||||
|
|
||||||
def router do
|
def router do
|
||||||
quote do
|
quote do
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,14 @@ defmodule ToweropsWeb.Endpoint do
|
||||||
only: ToweropsWeb.static_paths(),
|
only: ToweropsWeb.static_paths(),
|
||||||
raise_on_missing_only: code_reloading?
|
raise_on_missing_only: code_reloading?
|
||||||
|
|
||||||
|
# Generated coverage rasters live outside priv/static so the path can point
|
||||||
|
# at a shared NFS mount in production (see :coverage_storage_dir). Keep this
|
||||||
|
# plug after the main static plug so /coverage URLs only fall through here.
|
||||||
|
plug Plug.Static,
|
||||||
|
at: "/coverage",
|
||||||
|
from: Application.compile_env(:towerops, :coverage_storage_dir, {:towerops, "priv/static/coverage"}),
|
||||||
|
gzip: false
|
||||||
|
|
||||||
if Mix.env() == :dev do
|
if Mix.env() == :dev do
|
||||||
plug Tidewave
|
plug Tidewave
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue