package internal
import (
"encoding/json"
"net/http"
"os"
cachev3 "github.com/envoyproxy/go-control-plane/pkg/cache/v3"
"github.com/google/uuid"
)
// API holds reference to snapshot manager
type API struct {
Manager *SnapshotManager
}
// AddClusterRequest defines payload to add a cluster
type AddClusterRequest struct {
Name string `json:"name"`
}
// RemoveClusterRequest defines payload to remove a cluster
type RemoveClusterRequest struct {
Name string `json:"name"`
}
// AddRouteRequest defines payload to add a route
type AddRouteRequest struct {
Name string `json:"name"`
Cluster string `json:"cluster"`
PathPrefix string `json:"path_prefix"`
}
// RemoveRouteRequest defines payload to remove a route
type RemoveRouteRequest struct {
Name string `json:"name"`
}
// SnapshotFileRequest defines payload to load/save snapshot from/to file
type SnapshotFileRequest struct {
Path string `json:"path"`
}
// NewAPI returns a new REST API handler
func NewAPI(cache cachev3.SnapshotCache, nodeID string) *API {
return &API{
Manager: NewSnapshotManager(cache, nodeID),
}
}
// RegisterRoutes mounts REST handlers
func (api *API) RegisterRoutes(mux *http.ServeMux) {
mux.HandleFunc("/add-cluster", api.addCluster)
mux.HandleFunc("/remove-cluster", api.removeCluster)
mux.HandleFunc("/add-route", api.addRoute)
mux.HandleFunc("/remove-route", api.removeRoute)
mux.HandleFunc("/load-snapshot", api.loadSnapshot)
mux.HandleFunc("/save-snapshot", api.saveSnapshot)
}
// ---------------- Cluster Handlers ----------------
func (api *API) addCluster(w http.ResponseWriter, r *http.Request) {
var req AddClusterRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "invalid request", http.StatusBadRequest)
return
}
if req.Name == "" {
req.Name = uuid.NewString()
}
cluster := NewCluster(req.Name)
if err := api.Manager.AddCluster(cluster); err != nil {
http.Error(w, "failed to add cluster", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(map[string]string{"cuid": req.Name})
}
func (api *API) removeCluster(w http.ResponseWriter, r *http.Request) {
var req RemoveClusterRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Name == "" {
http.Error(w, "name required", http.StatusBadRequest)
return
}
if err := api.Manager.RemoveCluster(req.Name); err != nil {
http.Error(w, "failed to remove cluster", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}
// ---------------- Route Handlers ----------------
func (api *API) addRoute(w http.ResponseWriter, r *http.Request) {
var req AddRouteRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil ||
req.Name == "" || req.Cluster == "" || req.PathPrefix == "" {
http.Error(w, "invalid request", http.StatusBadRequest)
return
}
route := NewRoute(req.Name, req.Cluster, req.PathPrefix)
if err := api.Manager.AddRoute(route); err != nil {
http.Error(w, "failed to add route", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(map[string]string{"route": req.Name})
}
func (api *API) removeRoute(w http.ResponseWriter, r *http.Request) {
var req RemoveRouteRequest
// Decode request and check for required 'Name' field
if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Name == "" {
http.Error(w, "route name required", http.StatusBadRequest)
return
}
// Call the SnapshotManager's RemoveRoute method
if err := api.Manager.RemoveRoute(req.Name); err != nil {
// If the route doesn't exist, the manager handles the snapshot update anyway,
// so we mainly worry about cache read/write failures here.
http.Error(w, "failed to remove route", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}
// ---------------- Snapshot File Handlers ----------------
func (api *API) loadSnapshot(w http.ResponseWriter, r *http.Request) {
var req SnapshotFileRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Path == "" {
http.Error(w, "path required", http.StatusBadRequest)
return
}
if _, err := os.Stat(req.Path); os.IsNotExist(err) {
http.Error(w, "file not found", http.StatusBadRequest)
return
}
if err := api.Manager.LoadSnapshotFromFile(req.Path); err != nil {
http.Error(w, "failed to load snapshot", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}
func (api *API) saveSnapshot(w http.ResponseWriter, r *http.Request) {
var req SnapshotFileRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Path == "" {
http.Error(w, "path required", http.StatusBadRequest)
return
}
if err := api.Manager.SaveSnapshotToFile(req.Path); err != nil {
http.Error(w, "failed to save snapshot", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}