feat: polish & windviz & deploy
This commit is contained in:
parent
81b8e763bd
commit
465ad00f7b
78 changed files with 20622 additions and 2154 deletions
|
|
@ -1,9 +1,8 @@
|
|||
// Package api wires together every HTTP-facing component of the service:
|
||||
//
|
||||
// - Tawhiri-compatible v1 endpoints generated from the OpenAPI spec (ogen);
|
||||
// - The new v2 prediction endpoint;
|
||||
// - Dataset and job admin endpoints under /api/v1/admin/;
|
||||
// - Optional Prometheus-format metrics endpoint.
|
||||
// Package api is the HTTP surface of the service. Every REST operation is
|
||||
// defined in the OpenAPI spec (api/rest/predictor.swagger.yml) and served by
|
||||
// the ogen-generated server in pkg/rest; this package implements the
|
||||
// generated Handler interface and wires the server together with the
|
||||
// non-OpenAPI endpoints (Prometheus metrics, ReDoc docs).
|
||||
package api
|
||||
|
||||
import (
|
||||
|
|
@ -14,22 +13,22 @@ import (
|
|||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"predictor-refactored/internal/api/admin"
|
||||
"predictor-refactored/internal/api/async"
|
||||
"predictor-refactored/internal/api/docs"
|
||||
"predictor-refactored/internal/api/middleware"
|
||||
"predictor-refactored/internal/api/tawhiri"
|
||||
v2 "predictor-refactored/internal/api/v2"
|
||||
"predictor-refactored/internal/datasets"
|
||||
"predictor-refactored/internal/elevation"
|
||||
"predictor-refactored/internal/metrics"
|
||||
"predictor-refactored/internal/windviz"
|
||||
apirest "predictor-refactored/pkg/rest"
|
||||
)
|
||||
|
||||
// Server is the top-level HTTP server.
|
||||
type Server struct {
|
||||
port int
|
||||
mux *http.ServeMux
|
||||
log *zap.Logger
|
||||
port int
|
||||
mux *http.ServeMux
|
||||
async *async.Manager
|
||||
log *zap.Logger
|
||||
}
|
||||
|
||||
// Deps are the runtime dependencies the API layer needs.
|
||||
|
|
@ -39,8 +38,14 @@ type Deps struct {
|
|||
Metrics metrics.Sink
|
||||
MetricsHandler http.Handler // optional; mounted at MetricsPath when non-nil
|
||||
MetricsPath string
|
||||
AsyncManager *async.Manager // optional; mounts /api/v1/predictions when non-nil
|
||||
Log *zap.Logger
|
||||
EnableWind bool
|
||||
WindCache *windviz.Cache // optional; created if nil and EnableWind
|
||||
|
||||
AsyncWorkers int
|
||||
AsyncQueueSize int
|
||||
AsyncResultTTL time.Duration
|
||||
|
||||
Log *zap.Logger
|
||||
}
|
||||
|
||||
// New wires the HTTP server. The returned Server is not yet started.
|
||||
|
|
@ -51,53 +56,55 @@ func New(port int, d Deps) (*Server, error) {
|
|||
if d.Metrics == nil {
|
||||
d.Metrics = metrics.Noop()
|
||||
}
|
||||
if d.EnableWind && d.WindCache == nil {
|
||||
d.WindCache = windviz.NewCache(64, 10*time.Minute)
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
h := &Handler{
|
||||
mgr: d.Manager,
|
||||
elev: d.Elevation,
|
||||
metrics: d.Metrics,
|
||||
cache: d.WindCache,
|
||||
started: time.Now().UTC(),
|
||||
log: d.Log,
|
||||
}
|
||||
// The async worker pool runs the same prediction core as the synchronous
|
||||
// endpoint; inject it so async stays decoupled from the wire types.
|
||||
h.async = async.New(async.Config{
|
||||
Workers: d.AsyncWorkers,
|
||||
QueueSize: d.AsyncQueueSize,
|
||||
ResultTTL: d.AsyncResultTTL,
|
||||
}, h.runPredictionV2, d.Metrics, d.Log)
|
||||
|
||||
// ogen-generated server handles the Tawhiri-compat surface
|
||||
// (GET /api/v1/prediction and GET /ready).
|
||||
tw := tawhiri.New(d.Manager, d.Elevation, d.Metrics, d.Log)
|
||||
ogenSrv, err := apirest.NewServer(tw, apirest.WithMiddleware(middleware.OgenLogging(d.Log)))
|
||||
ogenSrv, err := apirest.NewServer(h, apirest.WithMiddleware(middleware.OgenLogging(d.Log)))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create ogen server: %w", err)
|
||||
}
|
||||
|
||||
// New primary prediction endpoint.
|
||||
v2h := v2.New(d.Manager, d.Elevation, d.Metrics, d.Log)
|
||||
mux.Handle("/api/v2/prediction", v2h)
|
||||
|
||||
// Admin endpoints.
|
||||
adminH := admin.New(d.Manager, d.Log)
|
||||
adminH.Register(mux)
|
||||
|
||||
// Async prediction endpoints (optional).
|
||||
if d.AsyncManager != nil {
|
||||
asyncH := async.NewHandler(d.AsyncManager)
|
||||
asyncH.Register(mux)
|
||||
}
|
||||
|
||||
// Metrics endpoint.
|
||||
mux := http.NewServeMux()
|
||||
// Liveness: always 200 while the process is up, independent of whether a
|
||||
// dataset is loaded. Container/orchestrator health checks use this; the
|
||||
// readiness of the data plane is /ready (an OpenAPI operation).
|
||||
mux.HandleFunc("GET /health", func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"status":"alive"}`))
|
||||
})
|
||||
docs.New().Register(mux)
|
||||
if d.MetricsHandler != nil && d.MetricsPath != "" {
|
||||
mux.Handle(d.MetricsPath, d.MetricsHandler)
|
||||
}
|
||||
|
||||
// Fallback to the ogen-generated routes (v1 + ready) for anything else.
|
||||
// The ogen server owns every OpenAPI route; mount it last as the catch-all.
|
||||
mux.Handle("/", ogenSrv)
|
||||
|
||||
return &Server{
|
||||
port: port,
|
||||
mux: mux,
|
||||
log: d.Log,
|
||||
}, nil
|
||||
return &Server{port: port, mux: mux, async: h.async, log: d.Log}, nil
|
||||
}
|
||||
|
||||
// Run starts the HTTP server and blocks until it returns.
|
||||
//
|
||||
// The handler chain is: CORS → request logger → mux.
|
||||
// Run starts the HTTP server and blocks until ctx is cancelled or the server
|
||||
// fails. The handler chain is CORS → mux (ogen routes + docs + metrics).
|
||||
func (s *Server) Run(ctx context.Context) error {
|
||||
srv := &http.Server{
|
||||
Addr: fmt.Sprintf(":%d", s.port),
|
||||
Handler: middleware.CORS(middleware.HTTPLogging(s.log, s.mux)),
|
||||
Handler: middleware.CORS(s.mux),
|
||||
}
|
||||
|
||||
errCh := make(chan error, 1)
|
||||
|
|
@ -115,3 +122,10 @@ func (s *Server) Run(ctx context.Context) error {
|
|||
return srv.Shutdown(shutdownCtx)
|
||||
}
|
||||
}
|
||||
|
||||
// Close releases background resources (the async worker pool).
|
||||
func (s *Server) Close() {
|
||||
if s.async != nil {
|
||||
s.async.Close()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue