Newer
Older
EnvoyControlPlane / internal / rest_api.go
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)
}