Files
rdp-proxy/backend/internal/modules/cluster/module.go
T
2026-05-12 21:02:29 +03:00

3391 lines
124 KiB
Go

package cluster
import (
"context"
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
"reflect"
"sort"
"strconv"
"strings"
"sync"
"time"
"github.com/go-chi/chi/v5"
"github.com/jackc/pgx/v5"
"github.com/example/remote-access-platform/backend/internal/platform/authority"
"github.com/example/remote-access-platform/backend/internal/platform/httpx"
"github.com/example/remote-access-platform/backend/internal/platform/module"
"github.com/example/remote-access-platform/backend/internal/platform/secrets"
)
type Module struct {
service *Service
vpnPacketHub *vpnPacketHub
vpnClientDiagnosticHub *vpnClientDiagnosticHub
}
func NewModule(deps module.Dependencies, verifiers ...*authority.Verifier) *Module {
store := NewPostgresStore(deps.Infra.DB, verifiers...)
if deps.Config.Secret.EncryptionKeyBase64 != "" {
if encryptor, err := secrets.NewEncryptor(deps.Config.Secret.EncryptionKeyBase64, deps.Config.Secret.EncryptionKeyID); err == nil {
store.WithClusterKeyEncryptor(encryptor)
}
}
return &Module{
service: NewService(store),
vpnPacketHub: newVPNPacketHub(),
vpnClientDiagnosticHub: newVPNClientDiagnosticHub(),
}
}
func (m *Module) Name() string {
return "cluster"
}
func (m *Module) RegisterRoutes(router chi.Router) {
router.Route("/clusters", func(r chi.Router) {
r.Get("/", m.listClusters)
r.Post("/", m.createCluster)
r.Get("/{clusterID}", m.getCluster)
r.Put("/{clusterID}", m.updateCluster)
r.Get("/{clusterID}/nodes", m.listClusterNodes)
r.Get("/{clusterID}/node-groups", m.listNodeGroups)
r.Post("/{clusterID}/node-groups", m.createNodeGroup)
r.Get("/{clusterID}/join-requests", m.listJoinRequests)
r.Post("/{clusterID}/join-requests", m.createJoinRequest)
r.Post("/{clusterID}/join-requests/{requestID}/approve", m.approveJoinRequest)
r.Post("/{clusterID}/join-requests/{requestID}/reject", m.rejectJoinRequest)
r.Get("/{clusterID}/join-tokens", m.listJoinTokens)
r.Post("/{clusterID}/join-tokens", m.createJoinToken)
r.Post("/{clusterID}/join-tokens/{tokenID}/revoke", m.revokeJoinToken)
r.Get("/{clusterID}/nodes/{nodeID}/roles", m.listNodeRoles)
r.Post("/{clusterID}/nodes/{nodeID}/roles", m.assignNodeRole)
r.Post("/{clusterID}/nodes/{nodeID}/heartbeats", m.recordHeartbeat)
r.Get("/{clusterID}/nodes/{nodeID}/heartbeats", m.listNodeHeartbeats)
r.Get("/{clusterID}/updates/releases", m.listReleaseVersions)
r.Post("/{clusterID}/updates/releases", m.createReleaseVersion)
r.Put("/{clusterID}/nodes/{nodeID}/updates/policy", m.upsertNodeUpdatePolicy)
r.Get("/{clusterID}/nodes/{nodeID}/updates/plan", m.getNodeUpdatePlan)
r.Post("/{clusterID}/nodes/{nodeID}/updates/status", m.reportNodeUpdateStatus)
r.Get("/{clusterID}/nodes/{nodeID}/updates/statuses", m.listNodeUpdateStatuses)
r.Get("/{clusterID}/nodes/{nodeID}/testing-flags", m.getEffectiveNodeTestingFlags)
r.Get("/{clusterID}/nodes/{nodeID}/mesh/synthetic-config", m.getNodeSyntheticMeshConfig)
r.Post("/{clusterID}/nodes/{nodeID}/telemetry", m.recordNodeTelemetry)
r.Get("/{clusterID}/nodes/{nodeID}/telemetry", m.listNodeTelemetry)
r.Post("/{clusterID}/nodes/{nodeID}/membership/attach", m.attachExistingNodeToCluster)
r.Put("/{clusterID}/nodes/{nodeID}/group", m.assignNodeGroup)
r.Post("/{clusterID}/nodes/{nodeID}/identity/revoke", m.revokeNodeIdentity)
r.Post("/{clusterID}/nodes/{nodeID}/membership/disable", m.disableMembership)
r.Delete("/{clusterID}/nodes/{nodeID}", m.deleteClusterNode)
r.Get("/{clusterID}/nodes/{nodeID}/workloads/desired", m.listDesiredWorkloads)
r.Put("/{clusterID}/nodes/{nodeID}/workloads/{serviceType}/desired", m.setDesiredWorkload)
r.Post("/{clusterID}/nodes/{nodeID}/workloads/{serviceType}/status", m.reportWorkloadStatus)
r.Get("/{clusterID}/nodes/{nodeID}/workloads/status", m.listWorkloadStatuses)
r.Get("/{clusterID}/mesh/links", m.listMeshLinks)
r.Post("/{clusterID}/mesh/links", m.reportMeshLink)
r.Get("/{clusterID}/mesh/route-intents", m.listRouteIntents)
r.Post("/{clusterID}/mesh/route-intents", m.createRouteIntent)
r.Post("/{clusterID}/mesh/route-intents/{routeIntentID}/expire", m.expireRouteIntent)
r.Post("/{clusterID}/mesh/route-intents/{routeIntentID}/disable", m.disableRouteIntent)
r.Get("/{clusterID}/mesh/qos-policies", m.listQoSPolicies)
r.Get("/{clusterID}/fabric/entry-points", m.listFabricEntryPoints)
r.Post("/{clusterID}/fabric/entry-points", m.createFabricEntryPoint)
r.Get("/{clusterID}/fabric/entry-points/{entryPointID}/nodes", m.listFabricEntryPointNodes)
r.Put("/{clusterID}/fabric/entry-points/{entryPointID}/nodes/{nodeID}", m.setFabricEntryPointNode)
r.Get("/{clusterID}/fabric/egress-pools", m.listFabricEgressPools)
r.Post("/{clusterID}/fabric/egress-pools", m.createFabricEgressPool)
r.Get("/{clusterID}/fabric/egress-pools/{egressPoolID}/nodes", m.listFabricEgressPoolNodes)
r.Put("/{clusterID}/fabric/egress-pools/{egressPoolID}/nodes/{nodeID}", m.setFabricEgressPoolNode)
r.Get("/{clusterID}/fabric/service-channels/route-feedback", m.listFabricServiceChannelRouteFeedback)
r.Post("/{clusterID}/fabric/service-channels/route-feedback/expire", m.expireFabricServiceChannelRouteFeedback)
r.Get("/{clusterID}/fabric/service-channels/rebuild-attempts", m.listFabricServiceChannelRouteRebuildAttempts)
r.Get("/{clusterID}/fabric/service-channels/rebuild-health", m.getFabricServiceChannelRouteRebuildHealthSummary)
r.Get("/{clusterID}/fabric/service-channels/readiness", m.getFabricServiceChannelReadiness)
r.Get("/{clusterID}/fabric/service-channels/schema-status", m.getFabricServiceChannelSchemaStatus)
r.Get("/{clusterID}/fabric/service-channels/rebuild-snapshots/health", m.getFabricServiceChannelRebuildSnapshotMaintenanceHealth)
r.Post("/{clusterID}/fabric/service-channels/rebuild-snapshots/warmup", m.warmupFabricServiceChannelRebuildSnapshots)
r.Get("/{clusterID}/fabric/service-channels/rebuild-incidents", m.listFabricServiceChannelRouteRebuildIncidents)
r.Post("/{clusterID}/fabric/service-channels/rebuild-incidents/investigations", m.recordFabricServiceChannelRouteRebuildInvestigation)
r.Get("/{clusterID}/fabric/service-channels/rebuild-investigations/breadcrumbs", m.listFabricServiceChannelRebuildInvestigationBreadcrumbs)
r.Get("/{clusterID}/fabric/service-channels/rebuild-health/silences", m.listFabricServiceChannelRouteRebuildAlertSilences)
r.Post("/{clusterID}/fabric/service-channels/rebuild-health/silences", m.silenceFabricServiceChannelRouteRebuildAlert)
r.Delete("/{clusterID}/fabric/service-channels/rebuild-health/silences/{silenceID}", m.unsilenceFabricServiceChannelRouteRebuildAlert)
r.Get("/{clusterID}/fabric/service-channels/recovery-policy", m.getFabricServiceChannelRecoveryPolicy)
r.Put("/{clusterID}/fabric/service-channels/recovery-policy", m.updateFabricServiceChannelRecoveryPolicy)
r.Get("/{clusterID}/fabric/service-channels/adaptive-policy", m.getFabricServiceChannelAdaptivePolicy)
r.Put("/{clusterID}/fabric/service-channels/adaptive-policy", m.updateFabricServiceChannelAdaptivePolicy)
r.Get("/{clusterID}/fabric/service-channels/pool-policy", m.getFabricServiceChannelPoolPolicy)
r.Put("/{clusterID}/fabric/service-channels/pool-policy", m.updateFabricServiceChannelPoolPolicy)
r.Get("/{clusterID}/fabric/service-channels/breadcrumb-window-policy", m.getFabricServiceChannelBreadcrumbWindowPolicy)
r.Put("/{clusterID}/fabric/service-channels/breadcrumb-window-policy", m.updateFabricServiceChannelBreadcrumbWindowPolicy)
r.Post("/{clusterID}/fabric/service-channels/leases", m.issueFabricServiceChannelLease)
r.Get("/{clusterID}/fabric/service-channels/leases", m.listFabricServiceChannelLeases)
r.Post("/{clusterID}/fabric/service-channels/leases/cleanup", m.cleanupFabricServiceChannelLeases)
r.Get("/{clusterID}/fabric/service-channels/access-telemetry", m.getFabricServiceChannelAccessTelemetry)
r.Post("/{clusterID}/fabric/service-channels/{channelID}/introspect", m.introspectFabricServiceChannelLease)
r.Post("/{clusterID}/vpn-connection-leases/expire-stale", m.expireStaleVPNConnectionLeases)
r.Get("/{clusterID}/vpn-connections", m.listVPNConnections)
r.Post("/{clusterID}/vpn-connections", m.createVPNConnection)
r.Get("/{clusterID}/vpn-connections/{vpnConnectionID}", m.getVPNConnection)
r.Put("/{clusterID}/vpn-connections/{vpnConnectionID}/desired-state", m.updateVPNConnectionDesiredState)
r.Get("/{clusterID}/vpn-connections/{vpnConnectionID}/allowed-nodes", m.listVPNConnectionAllowedNodes)
r.Put("/{clusterID}/vpn-connections/{vpnConnectionID}/allowed-nodes", m.setVPNConnectionAllowedNodes)
r.Get("/{clusterID}/vpn-connections/{vpnConnectionID}/route-policies", m.listVPNConnectionRoutePolicies)
r.Post("/{clusterID}/vpn-connections/{vpnConnectionID}/route-policies", m.upsertVPNConnectionRoutePolicy)
r.Get("/{clusterID}/vpn-connections/{vpnConnectionID}/leases/active", m.getActiveVPNConnectionLease)
r.Post("/{clusterID}/vpn-connections/{vpnConnectionID}/leases/acquire", m.acquireVPNConnectionLease)
r.Post("/{clusterID}/vpn-connections/{vpnConnectionID}/leases/{leaseID}/renew", m.renewVPNConnectionLease)
r.Post("/{clusterID}/vpn-connections/{vpnConnectionID}/leases/{leaseID}/release", m.releaseVPNConnectionLease)
r.Post("/{clusterID}/vpn-connections/{vpnConnectionID}/leases/{leaseID}/fence", m.fenceVPNConnectionLease)
r.Get("/{clusterID}/nodes/{nodeID}/vpn/assignments", m.listNodeVPNAssignments)
r.Post("/{clusterID}/nodes/{nodeID}/vpn/assignments/{vpnConnectionID}/lease/{leaseID}/renew", m.renewNodeVPNAssignmentLease)
r.Post("/{clusterID}/nodes/{nodeID}/vpn/assignments/{vpnConnectionID}/status", m.reportNodeVPNAssignmentStatus)
r.Get("/{clusterID}/vpn-connections/{vpnConnectionID}/tunnel/stats", m.getVPNPacketStats)
r.Post("/{clusterID}/vpn-connections/{vpnConnectionID}/tunnel/reset", m.resetVPNPacketQueues)
r.Post("/{clusterID}/vpn-connections/{vpnConnectionID}/tunnel/client/packets", m.postVPNClientPacket)
r.Get("/{clusterID}/vpn-connections/{vpnConnectionID}/tunnel/client/packets", m.getVPNClientPacket)
r.Post("/{clusterID}/vpn-connections/{vpnConnectionID}/tunnel/gateway/packets", m.postVPNGatewayPacket)
r.Get("/{clusterID}/vpn-connections/{vpnConnectionID}/tunnel/gateway/packets", m.getVPNGatewayPacket)
r.Get("/{clusterID}/vpn/client-diagnostics", m.listVPNClientDiagnosticStatuses)
r.Post("/{clusterID}/vpn/client-diagnostics/{deviceID}/status", m.reportVPNClientDiagnosticStatus)
r.Get("/{clusterID}/vpn/client-diagnostics/{deviceID}/status", m.getVPNClientDiagnosticStatus)
r.Post("/{clusterID}/vpn/client-diagnostics/{deviceID}/commands", m.enqueueVPNClientDiagnosticCommand)
r.Get("/{clusterID}/vpn/client-diagnostics/{deviceID}/commands", m.getVPNClientDiagnosticCommand)
r.Get("/{clusterID}/vpn/client-profile", m.getVPNClientProfile)
r.Get("/{clusterID}/authority", m.getClusterAuthority)
r.Put("/{clusterID}/authority", m.updateClusterAuthority)
r.Get("/{clusterID}/audit", m.listAuditEvents)
r.Get("/{clusterID}/events", m.streamClusterEvents)
})
router.Get("/cluster-admin-summaries", m.listClusterAdminSummaries)
router.Get("/fabric/testing-flags", m.listFabricTestingFlags)
router.Put("/fabric/testing-flags", m.upsertFabricTestingFlag)
}
func (m *Module) listClusters(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListClusters(r.Context(), r.URL.Query().Get("actor_user_id"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"clusters": items})
}
func (m *Module) getCluster(w http.ResponseWriter, r *http.Request) {
item, err := m.service.GetCluster(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"cluster": item})
}
func (m *Module) createCluster(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
Slug string `json:"slug"`
Name string `json:"name"`
Region *string `json:"region"`
Metadata json.RawMessage `json:"metadata"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid cluster payload")
return
}
item, err := m.service.CreateCluster(r.Context(), CreateClusterInput{
ActorUserID: payload.ActorUserID,
Slug: payload.Slug,
Name: payload.Name,
Region: payload.Region,
Metadata: payload.Metadata,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusCreated, map[string]any{"cluster": item})
}
func (m *Module) updateCluster(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
Name string `json:"name"`
Status string `json:"status"`
Region *string `json:"region"`
Metadata json.RawMessage `json:"metadata"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid cluster payload")
return
}
item, err := m.service.UpdateCluster(r.Context(), UpdateClusterInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
Name: payload.Name,
Status: payload.Status,
Region: payload.Region,
Metadata: payload.Metadata,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"cluster": item})
}
func (m *Module) listClusterNodes(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListClusterNodes(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"nodes": items})
}
func (m *Module) listNodeGroups(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListNodeGroups(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"node_groups": items})
}
func (m *Module) createNodeGroup(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
ParentGroupID *string `json:"parent_group_id"`
Name string `json:"name"`
Description *string `json:"description"`
SortOrder int `json:"sort_order"`
Metadata json.RawMessage `json:"metadata"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid node group payload")
return
}
item, err := m.service.CreateNodeGroup(r.Context(), CreateNodeGroupInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
ParentGroupID: payload.ParentGroupID,
Name: payload.Name,
Description: payload.Description,
SortOrder: payload.SortOrder,
Metadata: payload.Metadata,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusCreated, map[string]any{"node_group": item})
}
func (m *Module) createJoinToken(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
Scope json.RawMessage `json:"scope"`
ExpiresAt *time.Time `json:"expires_at"`
MaxUses int `json:"max_uses"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid join token payload")
return
}
expiresAt := time.Time{}
if payload.ExpiresAt != nil {
expiresAt = *payload.ExpiresAt
}
item, err := m.service.CreateJoinToken(r.Context(), CreateJoinTokenInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
Scope: payload.Scope,
ExpiresAt: expiresAt,
MaxUses: payload.MaxUses,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusCreated, map[string]any{"join_token": item})
}
func (m *Module) createJoinRequest(w http.ResponseWriter, r *http.Request) {
var payload struct {
JoinToken string `json:"join_token"`
NodeName string `json:"node_name"`
NodeFingerprint string `json:"node_fingerprint"`
PublicKey string `json:"public_key"`
ReportedCapabilities json.RawMessage `json:"reported_capabilities"`
ReportedFacts json.RawMessage `json:"reported_facts"`
RequestedRoles json.RawMessage `json:"requested_roles"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid join request payload")
return
}
item, err := m.service.CreateJoinRequest(r.Context(), CreateJoinRequestInput{
ClusterID: chi.URLParam(r, "clusterID"),
JoinToken: payload.JoinToken,
NodeName: payload.NodeName,
NodeFingerprint: payload.NodeFingerprint,
PublicKey: payload.PublicKey,
ReportedCapabilities: payload.ReportedCapabilities,
ReportedFacts: payload.ReportedFacts,
RequestedRoles: payload.RequestedRoles,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusCreated, map[string]any{"join_request": item})
}
func (m *Module) listJoinRequests(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListJoinRequests(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"join_requests": items})
}
func (m *Module) approveJoinRequest(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
NodeKey string `json:"node_key"`
OwnershipType string `json:"ownership_type"`
OwnerOrganizationID *string `json:"owner_organization_id"`
NodeGroupID *string `json:"node_group_id"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid join request approval payload")
return
}
item, err := m.service.ApproveJoinRequest(r.Context(), ApproveJoinRequestInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
JoinRequestID: chi.URLParam(r, "requestID"),
NodeKey: payload.NodeKey,
OwnershipType: payload.OwnershipType,
OwnerOrganizationID: payload.OwnerOrganizationID,
NodeGroupID: payload.NodeGroupID,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, item)
}
func (m *Module) rejectJoinRequest(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
Reason string `json:"reason"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid join request rejection payload")
return
}
item, err := m.service.RejectJoinRequest(r.Context(), RejectJoinRequestInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
JoinRequestID: chi.URLParam(r, "requestID"),
Reason: payload.Reason,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"join_request": item})
}
func (m *Module) revokeJoinToken(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid join token revoke payload")
return
}
item, err := m.service.RevokeJoinToken(r.Context(), RevokeJoinTokenInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
TokenID: chi.URLParam(r, "tokenID"),
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"join_token": item})
}
func (m *Module) listJoinTokens(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListJoinTokens(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"join_tokens": items})
}
func (m *Module) assignNodeRole(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
OrganizationID *string `json:"organization_id"`
Role string `json:"role"`
Status string `json:"status"`
Policy json.RawMessage `json:"policy"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid node role payload")
return
}
item, err := m.service.AssignNodeRole(r.Context(), AssignNodeRoleInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
NodeID: chi.URLParam(r, "nodeID"),
OrganizationID: payload.OrganizationID,
Role: payload.Role,
Status: payload.Status,
Policy: payload.Policy,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusCreated, map[string]any{"role_assignment": item})
}
func (m *Module) listNodeRoles(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListNodeRoleAssignments(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"), chi.URLParam(r, "nodeID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"role_assignments": items})
}
func (m *Module) recordHeartbeat(w http.ResponseWriter, r *http.Request) {
var payload struct {
HealthStatus string `json:"health_status"`
ReportedVersion *string `json:"reported_version"`
Capabilities json.RawMessage `json:"capabilities"`
ServiceStates json.RawMessage `json:"service_states"`
Metadata json.RawMessage `json:"metadata"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid node heartbeat payload")
return
}
item, err := m.service.RecordHeartbeat(r.Context(), RecordHeartbeatInput{
ClusterID: chi.URLParam(r, "clusterID"),
NodeID: chi.URLParam(r, "nodeID"),
HealthStatus: payload.HealthStatus,
ReportedVersion: payload.ReportedVersion,
Capabilities: payload.Capabilities,
ServiceStates: payload.ServiceStates,
Metadata: payload.Metadata,
})
if writeServiceError(w, err) {
return
}
flags, _ := m.service.GetEffectiveNodeTestingFlags(r.Context(), chi.URLParam(r, "clusterID"), chi.URLParam(r, "nodeID"))
updateHint := m.service.GetNodeUpdateHint(r.Context(), chi.URLParam(r, "clusterID"), chi.URLParam(r, "nodeID"))
httpx.WriteJSON(w, http.StatusAccepted, map[string]any{"heartbeat": item, "testing_flags": flags, "update_hint": updateHint})
}
func (m *Module) listNodeHeartbeats(w http.ResponseWriter, r *http.Request) {
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
items, err := m.service.ListNodeHeartbeats(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"), chi.URLParam(r, "nodeID"), limit)
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"heartbeats": items})
}
func (m *Module) createReleaseVersion(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
Product string `json:"product"`
Version string `json:"version"`
Channel string `json:"channel"`
Status string `json:"status"`
Compatibility json.RawMessage `json:"compatibility"`
Changelog *string `json:"changelog"`
Artifacts []ReleaseArtifactInput `json:"artifacts"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid release payload")
return
}
item, err := m.service.CreateReleaseVersion(r.Context(), CreateReleaseVersionInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
Product: payload.Product,
Version: payload.Version,
Channel: payload.Channel,
Status: payload.Status,
Compatibility: payload.Compatibility,
Changelog: payload.Changelog,
Artifacts: payload.Artifacts,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusCreated, map[string]any{"release_version": item})
}
func (m *Module) listReleaseVersions(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListReleaseVersions(
r.Context(),
r.URL.Query().Get("actor_user_id"),
chi.URLParam(r, "clusterID"),
r.URL.Query().Get("product"),
r.URL.Query().Get("channel"),
)
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"release_versions": items})
}
func (m *Module) upsertNodeUpdatePolicy(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
Product string `json:"product"`
Channel string `json:"channel"`
TargetVersion *string `json:"target_version"`
Strategy string `json:"strategy"`
Enabled bool `json:"enabled"`
RollbackAllowed bool `json:"rollback_allowed"`
HealthWindowSec int `json:"health_window_seconds"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid update policy payload")
return
}
item, err := m.service.UpsertNodeUpdatePolicy(r.Context(), UpsertNodeUpdatePolicyInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
NodeID: chi.URLParam(r, "nodeID"),
Product: payload.Product,
Channel: payload.Channel,
TargetVersion: payload.TargetVersion,
Strategy: payload.Strategy,
Enabled: payload.Enabled,
RollbackAllowed: payload.RollbackAllowed,
HealthWindowSec: payload.HealthWindowSec,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"node_update_policy": item})
}
func (m *Module) getNodeUpdatePlan(w http.ResponseWriter, r *http.Request) {
item, err := m.service.GetNodeUpdatePlan(r.Context(), GetNodeUpdatePlanInput{
ClusterID: chi.URLParam(r, "clusterID"),
NodeID: chi.URLParam(r, "nodeID"),
Product: r.URL.Query().Get("product"),
CurrentVersion: r.URL.Query().Get("current_version"),
OS: r.URL.Query().Get("os"),
Arch: r.URL.Query().Get("arch"),
InstallType: r.URL.Query().Get("install_type"),
Channel: r.URL.Query().Get("channel"),
ArtifactOrigin: requestOrigin(r),
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"node_update_plan": item})
}
func requestOrigin(r *http.Request) string {
proto := strings.TrimSpace(r.Header.Get("X-Forwarded-Proto"))
if proto == "" {
proto = strings.TrimSpace(r.Header.Get("X-Forwarded-Scheme"))
}
if proto == "" {
if r.TLS != nil {
proto = "https"
} else {
proto = "http"
}
}
host := strings.TrimSpace(r.Header.Get("X-Forwarded-Host"))
if host == "" {
host = strings.TrimSpace(r.Host)
}
if host == "" {
return ""
}
if comma := strings.Index(host, ","); comma >= 0 {
host = strings.TrimSpace(host[:comma])
}
if comma := strings.Index(proto, ","); comma >= 0 {
proto = strings.TrimSpace(proto[:comma])
}
if !strings.Contains(host, ":") {
if port := strings.TrimSpace(r.Header.Get("X-Forwarded-Port")); port != "" && port != "80" && port != "443" {
host += ":" + port
}
}
if proto == "" || host == "" {
return ""
}
return remapDirectBackendDownloadOrigin(proto, host)
}
func remapDirectBackendDownloadOrigin(proto, host string) string {
httpPort := strings.TrimSpace(os.Getenv("HTTP_PORT"))
if httpPort == "" {
httpPort = "18121"
}
downloadPort := strings.TrimSpace(os.Getenv("RAP_DOWNLOAD_PORT"))
if downloadPort == "" {
downloadPort = "18080"
}
suffix := ":" + httpPort
if !strings.HasSuffix(host, suffix) {
return proto + "://" + host
}
hostOnly := strings.TrimSuffix(host, suffix)
return proto + "://" + hostOnly + ":" + downloadPort
}
func (m *Module) reportNodeUpdateStatus(w http.ResponseWriter, r *http.Request) {
var payload struct {
Product string `json:"product"`
CurrentVersion string `json:"current_version"`
TargetVersion string `json:"target_version"`
Phase string `json:"phase"`
Status string `json:"status"`
AttemptID string `json:"attempt_id"`
ErrorMessage *string `json:"error_message"`
RollbackVersion *string `json:"rollback_version"`
Payload json.RawMessage `json:"payload"`
ObservedAt *time.Time `json:"observed_at"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid update status payload")
return
}
observedAt := time.Time{}
if payload.ObservedAt != nil {
observedAt = *payload.ObservedAt
}
item, err := m.service.ReportNodeUpdateStatus(r.Context(), ReportNodeUpdateStatusInput{
ClusterID: chi.URLParam(r, "clusterID"),
NodeID: chi.URLParam(r, "nodeID"),
Product: payload.Product,
CurrentVersion: payload.CurrentVersion,
TargetVersion: payload.TargetVersion,
Phase: payload.Phase,
Status: payload.Status,
AttemptID: payload.AttemptID,
ErrorMessage: payload.ErrorMessage,
RollbackVersion: payload.RollbackVersion,
Payload: payload.Payload,
ObservedAt: observedAt,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusAccepted, map[string]any{"node_update_status": item})
}
func (m *Module) listNodeUpdateStatuses(w http.ResponseWriter, r *http.Request) {
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
items, err := m.service.ListNodeUpdateStatuses(
r.Context(),
r.URL.Query().Get("actor_user_id"),
chi.URLParam(r, "clusterID"),
chi.URLParam(r, "nodeID"),
limit,
)
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"node_update_statuses": items})
}
func (m *Module) getEffectiveNodeTestingFlags(w http.ResponseWriter, r *http.Request) {
item, err := m.service.GetEffectiveNodeTestingFlags(r.Context(), chi.URLParam(r, "clusterID"), chi.URLParam(r, "nodeID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"testing_flags": item})
}
func (m *Module) getNodeSyntheticMeshConfig(w http.ResponseWriter, r *http.Request) {
item, err := m.service.GetNodeSyntheticMeshConfig(r.Context(), GetNodeSyntheticMeshConfigInput{
ClusterID: chi.URLParam(r, "clusterID"),
NodeID: chi.URLParam(r, "nodeID"),
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"synthetic_mesh_config": item})
}
func (m *Module) recordNodeTelemetry(w http.ResponseWriter, r *http.Request) {
var payload struct {
CPUPercent *float64 `json:"cpu_percent"`
MemoryUsedBytes *int64 `json:"memory_used_bytes"`
MemoryTotalBytes *int64 `json:"memory_total_bytes"`
DiskUsedBytes *int64 `json:"disk_used_bytes"`
DiskTotalBytes *int64 `json:"disk_total_bytes"`
NetworkRxBytes *int64 `json:"network_rx_bytes"`
NetworkTxBytes *int64 `json:"network_tx_bytes"`
ProcessCount *int `json:"process_count"`
Payload json.RawMessage `json:"payload"`
ObservedAt *time.Time `json:"observed_at"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid node telemetry payload")
return
}
observedAt := time.Time{}
if payload.ObservedAt != nil {
observedAt = *payload.ObservedAt
}
item, err := m.service.RecordNodeTelemetry(r.Context(), RecordNodeTelemetryInput{
ClusterID: chi.URLParam(r, "clusterID"),
NodeID: chi.URLParam(r, "nodeID"),
CPUPercent: payload.CPUPercent,
MemoryUsedBytes: payload.MemoryUsedBytes,
MemoryTotalBytes: payload.MemoryTotalBytes,
DiskUsedBytes: payload.DiskUsedBytes,
DiskTotalBytes: payload.DiskTotalBytes,
NetworkRxBytes: payload.NetworkRxBytes,
NetworkTxBytes: payload.NetworkTxBytes,
ProcessCount: payload.ProcessCount,
Payload: payload.Payload,
ObservedAt: observedAt,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusAccepted, map[string]any{"telemetry": item})
}
func (m *Module) listNodeTelemetry(w http.ResponseWriter, r *http.Request) {
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
items, err := m.service.ListNodeTelemetry(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"), chi.URLParam(r, "nodeID"), limit)
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"telemetry": items})
}
func (m *Module) attachExistingNodeToCluster(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
Roles []string `json:"roles"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid membership attach payload")
return
}
item, err := m.service.AttachExistingNodeToCluster(r.Context(), AttachExistingNodeInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
NodeID: chi.URLParam(r, "nodeID"),
Roles: payload.Roles,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"node": item})
}
func (m *Module) assignNodeGroup(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
GroupID *string `json:"group_id"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid node group assignment payload")
return
}
item, err := m.service.AssignNodeToGroup(r.Context(), AssignNodeGroupInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
NodeID: chi.URLParam(r, "nodeID"),
GroupID: payload.GroupID,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"node": item})
}
func (m *Module) revokeNodeIdentity(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
Reason string `json:"reason"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid node identity revoke payload")
return
}
err := m.service.RevokeNodeIdentity(r.Context(), RevokeNodeIdentityInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
NodeID: chi.URLParam(r, "nodeID"),
Reason: payload.Reason,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusAccepted, map[string]any{"status": "accepted"})
}
func (m *Module) disableMembership(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
Reason string `json:"reason"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid membership disable payload")
return
}
err := m.service.DisableClusterMembership(r.Context(), DisableMembershipInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
NodeID: chi.URLParam(r, "nodeID"),
Reason: payload.Reason,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusAccepted, map[string]any{"status": "accepted"})
}
func (m *Module) deleteClusterNode(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
Reason string `json:"reason"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid node delete payload")
return
}
err := m.service.DeleteClusterNode(r.Context(), DeleteClusterNodeInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
NodeID: chi.URLParam(r, "nodeID"),
Reason: payload.Reason,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusAccepted, map[string]any{"status": "accepted"})
}
func (m *Module) setDesiredWorkload(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
DesiredState string `json:"desired_state"`
Version *string `json:"version"`
RuntimeMode string `json:"runtime_mode"`
ArtifactRef *string `json:"artifact_ref"`
Config json.RawMessage `json:"config"`
Environment json.RawMessage `json:"environment"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid desired workload payload")
return
}
item, err := m.service.SetDesiredWorkload(r.Context(), SetDesiredWorkloadInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
NodeID: chi.URLParam(r, "nodeID"),
ServiceType: chi.URLParam(r, "serviceType"),
DesiredState: payload.DesiredState,
Version: payload.Version,
RuntimeMode: payload.RuntimeMode,
ArtifactRef: payload.ArtifactRef,
Config: payload.Config,
Environment: payload.Environment,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"desired_workload": item})
}
func (m *Module) listDesiredWorkloads(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListDesiredWorkloads(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"), chi.URLParam(r, "nodeID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"desired_workloads": items})
}
func (m *Module) reportWorkloadStatus(w http.ResponseWriter, r *http.Request) {
var payload struct {
ReportedState string `json:"reported_state"`
RuntimeMode string `json:"runtime_mode"`
Version *string `json:"version"`
StatusPayload json.RawMessage `json:"status_payload"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid workload status payload")
return
}
item, err := m.service.ReportWorkloadStatus(r.Context(), ReportWorkloadStatusInput{
ClusterID: chi.URLParam(r, "clusterID"),
NodeID: chi.URLParam(r, "nodeID"),
ServiceType: chi.URLParam(r, "serviceType"),
ReportedState: payload.ReportedState,
RuntimeMode: payload.RuntimeMode,
Version: payload.Version,
StatusPayload: payload.StatusPayload,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusAccepted, map[string]any{"workload_status": item})
}
func (m *Module) listWorkloadStatuses(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListLatestWorkloadStatuses(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"), chi.URLParam(r, "nodeID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"workload_statuses": items})
}
func (m *Module) reportMeshLink(w http.ResponseWriter, r *http.Request) {
var payload struct {
SourceNodeID string `json:"source_node_id"`
TargetNodeID string `json:"target_node_id"`
LinkStatus string `json:"link_status"`
LatencyMs *int `json:"latency_ms"`
QualityScore *int `json:"quality_score"`
Metadata json.RawMessage `json:"metadata"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid mesh link payload")
return
}
item, err := m.service.ReportMeshLink(r.Context(), ReportMeshLinkInput{
ClusterID: chi.URLParam(r, "clusterID"),
SourceNodeID: payload.SourceNodeID,
TargetNodeID: payload.TargetNodeID,
LinkStatus: payload.LinkStatus,
LatencyMs: payload.LatencyMs,
QualityScore: payload.QualityScore,
Metadata: payload.Metadata,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusAccepted, map[string]any{"mesh_link": item})
}
func (m *Module) listMeshLinks(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListMeshLinks(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"mesh_links": items})
}
func (m *Module) createRouteIntent(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
SourceSelector json.RawMessage `json:"source_selector"`
DestinationSelector json.RawMessage `json:"destination_selector"`
ServiceClass string `json:"service_class"`
Priority int `json:"priority"`
Policy json.RawMessage `json:"policy"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid route intent payload")
return
}
item, err := m.service.CreateRouteIntent(r.Context(), CreateRouteIntentInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
SourceSelector: payload.SourceSelector,
DestinationSelector: payload.DestinationSelector,
ServiceClass: payload.ServiceClass,
Priority: payload.Priority,
Policy: payload.Policy,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusCreated, map[string]any{"route_intent": item})
}
func (m *Module) listRouteIntents(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListRouteIntents(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"route_intents": items})
}
func (m *Module) expireRouteIntent(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
Reason string `json:"reason"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid route intent expire payload")
return
}
item, err := m.service.ExpireRouteIntent(r.Context(), RouteIntentLifecycleInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
RouteIntentID: chi.URLParam(r, "routeIntentID"),
Reason: payload.Reason,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusAccepted, map[string]any{"route_intent": item})
}
func (m *Module) disableRouteIntent(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
Reason string `json:"reason"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid route intent disable payload")
return
}
item, err := m.service.DisableRouteIntent(r.Context(), RouteIntentLifecycleInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
RouteIntentID: chi.URLParam(r, "routeIntentID"),
Reason: payload.Reason,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusAccepted, map[string]any{"route_intent": item})
}
func (m *Module) listQoSPolicies(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListQoSPolicies(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"qos_policies": items})
}
func (m *Module) listFabricEntryPoints(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListFabricEntryPoints(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"entry_points": items})
}
func (m *Module) createFabricEntryPoint(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
Name string `json:"name"`
Status string `json:"status"`
EndpointType string `json:"endpoint_type"`
PublicEndpoint *string `json:"public_endpoint"`
Policy json.RawMessage `json:"policy"`
Metadata json.RawMessage `json:"metadata"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid fabric entry point payload")
return
}
item, err := m.service.CreateFabricEntryPoint(r.Context(), CreateFabricEntryPointInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
Name: payload.Name,
Status: payload.Status,
EndpointType: payload.EndpointType,
PublicEndpoint: payload.PublicEndpoint,
Policy: payload.Policy,
Metadata: payload.Metadata,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusCreated, map[string]any{"entry_point": item})
}
func (m *Module) setFabricEntryPointNode(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
Status string `json:"status"`
Priority int `json:"priority"`
Metadata json.RawMessage `json:"metadata"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid fabric entry point node payload")
return
}
item, err := m.service.SetFabricEntryPointNode(r.Context(), SetFabricEntryPointNodeInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
EntryPointID: chi.URLParam(r, "entryPointID"),
NodeID: chi.URLParam(r, "nodeID"),
Status: payload.Status,
Priority: payload.Priority,
Metadata: payload.Metadata,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"entry_point_node": item})
}
func (m *Module) listFabricEntryPointNodes(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListFabricEntryPointNodes(
r.Context(),
r.URL.Query().Get("actor_user_id"),
chi.URLParam(r, "clusterID"),
chi.URLParam(r, "entryPointID"),
)
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"entry_point_nodes": items})
}
func (m *Module) listFabricEgressPools(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListFabricEgressPools(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"egress_pools": items})
}
func (m *Module) createFabricEgressPool(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
Name string `json:"name"`
Status string `json:"status"`
Description *string `json:"description"`
RouteScope json.RawMessage `json:"route_scope"`
Policy json.RawMessage `json:"policy"`
Metadata json.RawMessage `json:"metadata"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid fabric egress pool payload")
return
}
item, err := m.service.CreateFabricEgressPool(r.Context(), CreateFabricEgressPoolInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
Name: payload.Name,
Status: payload.Status,
Description: payload.Description,
RouteScope: payload.RouteScope,
Policy: payload.Policy,
Metadata: payload.Metadata,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusCreated, map[string]any{"egress_pool": item})
}
func (m *Module) setFabricEgressPoolNode(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
Status string `json:"status"`
Priority int `json:"priority"`
Metadata json.RawMessage `json:"metadata"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid fabric egress pool node payload")
return
}
item, err := m.service.SetFabricEgressPoolNode(r.Context(), SetFabricEgressPoolNodeInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
EgressPoolID: chi.URLParam(r, "egressPoolID"),
NodeID: chi.URLParam(r, "nodeID"),
Status: payload.Status,
Priority: payload.Priority,
Metadata: payload.Metadata,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"egress_pool_node": item})
}
func (m *Module) listFabricEgressPoolNodes(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListFabricEgressPoolNodes(
r.Context(),
r.URL.Query().Get("actor_user_id"),
chi.URLParam(r, "clusterID"),
chi.URLParam(r, "egressPoolID"),
)
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"egress_pool_nodes": items})
}
func (m *Module) issueFabricServiceChannelLease(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
OrganizationID string `json:"organization_id"`
UserID string `json:"user_id"`
ResourceID string `json:"resource_id"`
ServiceClass string `json:"service_class"`
EntryNodeIDs []string `json:"entry_node_ids"`
ExitNodeIDs []string `json:"exit_node_ids"`
PreferredEntryNodeID string `json:"preferred_entry_node_id"`
PreferredExitNodeID string `json:"preferred_exit_node_id"`
RequiredRoles []string `json:"required_roles"`
AllowedChannels []string `json:"allowed_channels"`
QoS json.RawMessage `json:"qos"`
Failover json.RawMessage `json:"failover"`
Metadata json.RawMessage `json:"metadata"`
TTLSeconds int `json:"ttl_seconds"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid fabric service channel lease payload")
return
}
item, err := m.service.IssueFabricServiceChannelLease(r.Context(), IssueFabricServiceChannelLeaseInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
OrganizationID: payload.OrganizationID,
UserID: payload.UserID,
ResourceID: payload.ResourceID,
ServiceClass: payload.ServiceClass,
EntryNodeIDs: payload.EntryNodeIDs,
ExitNodeIDs: payload.ExitNodeIDs,
PreferredEntryNodeID: payload.PreferredEntryNodeID,
PreferredExitNodeID: payload.PreferredExitNodeID,
RequiredRoles: payload.RequiredRoles,
AllowedChannels: payload.AllowedChannels,
QoS: payload.QoS,
Failover: payload.Failover,
Metadata: payload.Metadata,
TTL: time.Duration(payload.TTLSeconds) * time.Second,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusCreated, map[string]any{"fabric_service_channel_lease": item})
}
func (m *Module) introspectFabricServiceChannelLease(w http.ResponseWriter, r *http.Request) {
var payload struct {
Token string `json:"token"`
ResourceID string `json:"resource_id"`
ServiceClass string `json:"service_class"`
ChannelClass string `json:"channel_class"`
EntryNodeID string `json:"entry_node_id"`
RequestSourceIP string `json:"request_source_ip"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid fabric service channel introspection payload")
return
}
if payload.EntryNodeID == "" {
payload.EntryNodeID = r.Header.Get("X-RAP-Entry-Node")
}
if payload.RequestSourceIP == "" {
payload.RequestSourceIP = r.RemoteAddr
}
item, err := m.service.IntrospectFabricServiceChannelLease(r.Context(), IntrospectFabricServiceChannelLeaseInput{
ClusterID: chi.URLParam(r, "clusterID"),
ChannelID: chi.URLParam(r, "channelID"),
ResourceID: payload.ResourceID,
ServiceClass: payload.ServiceClass,
ChannelClass: payload.ChannelClass,
Token: payload.Token,
EntryNodeID: payload.EntryNodeID,
RequestSourceIP: payload.RequestSourceIP,
})
if writeServiceError(w, err) {
return
}
status := http.StatusOK
if !item.Allowed {
status = http.StatusForbidden
}
httpx.WriteJSON(w, status, map[string]any{"fabric_service_channel_introspection": item})
}
func (m *Module) listFabricServiceChannelLeases(w http.ResponseWriter, r *http.Request) {
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
items, err := m.service.ListFabricServiceChannelLeases(r.Context(), r.URL.Query().Get("actor_user_id"), ListFabricServiceChannelLeasesInput{
ClusterID: chi.URLParam(r, "clusterID"),
ServiceClass: r.URL.Query().Get("service_class"),
EntryNodeID: r.URL.Query().Get("entry_node_id"),
ResourceID: r.URL.Query().Get("resource_id"),
IncludeExpired: r.URL.Query().Get("include_expired") == "true",
Limit: limit,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"fabric_service_channel_lease_maintenance": items})
}
func (m *Module) cleanupFabricServiceChannelLeases(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
Limit int `json:"limit"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil && !errors.Is(err, io.EOF) {
httpx.WriteError(w, http.StatusBadRequest, "invalid fabric service channel lease cleanup payload")
return
}
result, err := m.service.CleanupFabricServiceChannelLeases(r.Context(), CleanupFabricServiceChannelLeasesInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
Limit: payload.Limit,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusAccepted, map[string]any{"fabric_service_channel_lease_maintenance": result})
}
func (m *Module) getFabricServiceChannelAccessTelemetry(w http.ResponseWriter, r *http.Request) {
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
item, err := m.service.GetFabricServiceChannelAccessTelemetry(r.Context(), r.URL.Query().Get("actor_user_id"), GetFabricServiceChannelAccessTelemetryInput{
ClusterID: chi.URLParam(r, "clusterID"),
Limit: limit,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"fabric_service_channel_access_telemetry": item})
}
func (m *Module) listFabricServiceChannelRouteFeedback(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListFabricServiceChannelRouteFeedback(r.Context(), r.URL.Query().Get("actor_user_id"), ListFabricServiceChannelRouteFeedbackInput{
ClusterID: chi.URLParam(r, "clusterID"),
ReporterNodeID: r.URL.Query().Get("reporter_node_id"),
RouteID: r.URL.Query().Get("route_id"),
ServiceClass: r.URL.Query().Get("service_class"),
FeedbackStatus: r.URL.Query().Get("feedback_status"),
IncludeExpired: r.URL.Query().Get("include_expired") == "true",
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"route_feedback": items})
}
func (m *Module) listFabricServiceChannelRouteRebuildAttempts(w http.ResponseWriter, r *http.Request) {
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
offset, _ := strconv.Atoi(r.URL.Query().Get("offset"))
items, err := m.service.ListFabricServiceChannelRouteRebuildAttempts(r.Context(), r.URL.Query().Get("actor_user_id"), ListFabricServiceChannelRouteRebuildAttemptsInput{
ClusterID: chi.URLParam(r, "clusterID"),
ReporterNodeID: r.URL.Query().Get("reporter_node_id"),
RouteID: r.URL.Query().Get("route_id"),
ReplacementRouteID: r.URL.Query().Get("replacement_route_id"),
ServiceClass: r.URL.Query().Get("service_class"),
RebuildStatus: r.URL.Query().Get("rebuild_status"),
RebuildRequestID: r.URL.Query().Get("rebuild_request_id"),
Generation: r.URL.Query().Get("generation"),
FeedbackSource: r.URL.Query().Get("feedback_source"),
FeedbackChannelID: r.URL.Query().Get("feedback_channel_id"),
FeedbackViolationStatus: r.URL.Query().Get("feedback_violation_status"),
EnrichmentMode: r.URL.Query().Get("enrichment"),
Limit: limit,
Offset: offset,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"rebuild_attempts": items})
}
func (m *Module) getFabricServiceChannelRouteRebuildHealthSummary(w http.ResponseWriter, r *http.Request) {
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
summary, err := m.service.GetFabricServiceChannelRouteRebuildHealthSummary(r.Context(), r.URL.Query().Get("actor_user_id"), GetFabricServiceChannelRouteRebuildHealthSummaryInput{
ClusterID: chi.URLParam(r, "clusterID"),
Limit: limit,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"rebuild_health": summary})
}
func (m *Module) getFabricServiceChannelReadiness(w http.ResponseWriter, r *http.Request) {
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
readiness, err := m.service.GetFabricServiceChannelReadiness(r.Context(), r.URL.Query().Get("actor_user_id"), GetFabricServiceChannelReadinessInput{
ClusterID: chi.URLParam(r, "clusterID"),
Limit: limit,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"fabric_service_channel_readiness": readiness})
}
func (m *Module) getFabricServiceChannelSchemaStatus(w http.ResponseWriter, r *http.Request) {
status, err := m.service.GetFabricServiceChannelSchemaStatus(r.Context(), r.URL.Query().Get("actor_user_id"), GetFabricServiceChannelSchemaStatusInput{
ClusterID: chi.URLParam(r, "clusterID"),
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"fabric_service_channel_schema_status": status})
}
func (m *Module) getFabricServiceChannelRebuildSnapshotMaintenanceHealth(w http.ResponseWriter, r *http.Request) {
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
minAgeSeconds, _ := strconv.ParseInt(r.URL.Query().Get("min_age_seconds"), 10, 64)
heartbeatThreshold, _ := strconv.Atoi(r.URL.Query().Get("heartbeat_threshold"))
health, err := m.service.GetFabricServiceChannelRebuildSnapshotMaintenanceHealth(r.Context(), r.URL.Query().Get("actor_user_id"), GetFabricServiceChannelRebuildSnapshotMaintenanceHealthInput{
ClusterID: chi.URLParam(r, "clusterID"),
Limit: limit,
MinAgeSeconds: minAgeSeconds,
HeartbeatThreshold: heartbeatThreshold,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"rebuild_snapshot_health": health})
}
func (m *Module) warmupFabricServiceChannelRebuildSnapshots(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
Limit int `json:"limit"`
StaleAfterSeconds int64 `json:"stale_after_seconds"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil && !errors.Is(err, io.EOF) {
httpx.WriteError(w, http.StatusBadRequest, "invalid rebuild snapshot warmup payload")
return
}
result, err := m.service.WarmupFabricServiceChannelRebuildSnapshots(r.Context(), WarmupFabricServiceChannelRebuildSnapshotsInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
Limit: payload.Limit,
StaleAfterSeconds: payload.StaleAfterSeconds,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusAccepted, map[string]any{"rebuild_snapshot_warmup": result})
}
func (m *Module) listFabricServiceChannelRouteRebuildIncidents(w http.ResponseWriter, r *http.Request) {
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
incidents, err := m.service.ListFabricServiceChannelRouteRebuildIncidents(r.Context(), r.URL.Query().Get("actor_user_id"), ListFabricServiceChannelRouteRebuildIncidentsInput{
ClusterID: chi.URLParam(r, "clusterID"),
Limit: limit,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"rebuild_incidents": incidents})
}
func (m *Module) recordFabricServiceChannelRouteRebuildInvestigation(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
ReporterNodeID string `json:"reporter_node_id"`
RouteID string `json:"route_id"`
ServiceClass string `json:"service_class"`
Generation string `json:"generation"`
GuardStatus string `json:"guard_status"`
IncidentID string `json:"incident_id"`
FeedbackSource string `json:"feedback_source"`
FeedbackChannelID string `json:"feedback_channel_id"`
FeedbackViolationStatus string `json:"feedback_violation_status"`
DrilldownSource string `json:"drilldown_source"`
Reason string `json:"reason"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid rebuild investigation payload")
return
}
if err := m.service.RecordFabricServiceChannelRouteRebuildInvestigation(r.Context(), RecordFabricServiceChannelRouteRebuildInvestigationInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
ReporterNodeID: payload.ReporterNodeID,
RouteID: payload.RouteID,
ServiceClass: payload.ServiceClass,
Generation: payload.Generation,
GuardStatus: payload.GuardStatus,
IncidentID: payload.IncidentID,
FeedbackSource: payload.FeedbackSource,
FeedbackChannelID: payload.FeedbackChannelID,
FeedbackViolationStatus: payload.FeedbackViolationStatus,
DrilldownSource: payload.DrilldownSource,
Reason: payload.Reason,
}); writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusCreated, map[string]any{"status": "recorded"})
}
func (m *Module) listFabricServiceChannelRebuildInvestigationBreadcrumbs(w http.ResponseWriter, r *http.Request) {
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
currentWindowSeconds, _ := strconv.ParseInt(r.URL.Query().Get("current_window_seconds"), 10, 64)
historyWindowSeconds, _ := strconv.ParseInt(r.URL.Query().Get("history_window_seconds"), 10, 64)
breadcrumbs, err := m.service.ListFabricServiceChannelRebuildInvestigationBreadcrumbs(r.Context(), r.URL.Query().Get("actor_user_id"), ListFabricServiceChannelRebuildInvestigationBreadcrumbsInput{
ClusterID: chi.URLParam(r, "clusterID"),
Limit: limit,
CurrentWindowSeconds: currentWindowSeconds,
HistoryWindowSeconds: historyWindowSeconds,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"rebuild_investigation_breadcrumbs": breadcrumbs})
}
func (m *Module) silenceFabricServiceChannelRouteRebuildAlert(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
IncidentSource string `json:"incident_source"`
ChannelID string `json:"channel_id"`
ReporterNodeID string `json:"reporter_node_id"`
RouteID string `json:"route_id"`
GuardStatus string `json:"guard_status"`
Generation string `json:"generation"`
Reason string `json:"reason"`
TTLSeconds int64 `json:"ttl_seconds"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid rebuild alert silence payload")
return
}
silence, err := m.service.SilenceFabricServiceChannelRouteRebuildAlert(r.Context(), SilenceFabricServiceChannelRouteRebuildAlertInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
IncidentSource: payload.IncidentSource,
ChannelID: payload.ChannelID,
ReporterNodeID: payload.ReporterNodeID,
RouteID: payload.RouteID,
GuardStatus: payload.GuardStatus,
Generation: payload.Generation,
Reason: payload.Reason,
TTL: time.Duration(payload.TTLSeconds) * time.Second,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusCreated, map[string]any{"rebuild_alert_silence": silence})
}
func (m *Module) listFabricServiceChannelRouteRebuildAlertSilences(w http.ResponseWriter, r *http.Request) {
silences, err := m.service.ListFabricServiceChannelRouteRebuildAlertSilences(
r.Context(),
r.URL.Query().Get("actor_user_id"),
chi.URLParam(r, "clusterID"),
time.Time{},
)
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"rebuild_alert_silences": silences})
}
func (m *Module) unsilenceFabricServiceChannelRouteRebuildAlert(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
Reason string `json:"reason"`
}
if r.Body != nil {
_ = json.NewDecoder(r.Body).Decode(&payload)
}
if payload.ActorUserID == "" {
payload.ActorUserID = r.URL.Query().Get("actor_user_id")
}
silence, err := m.service.UnsilenceFabricServiceChannelRouteRebuildAlert(r.Context(), UnsilenceFabricServiceChannelRouteRebuildAlertInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
SilenceID: chi.URLParam(r, "silenceID"),
Reason: payload.Reason,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"rebuild_alert_silence": silence})
}
func (m *Module) expireFabricServiceChannelRouteFeedback(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
ReporterNodeID string `json:"reporter_node_id"`
RouteID string `json:"route_id"`
ServiceClass string `json:"service_class"`
Reason string `json:"reason"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid route feedback expire payload")
return
}
result, err := m.service.ExpireFabricServiceChannelRouteFeedback(r.Context(), ExpireFabricServiceChannelRouteFeedbackInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
ReporterNodeID: payload.ReporterNodeID,
RouteID: payload.RouteID,
ServiceClass: payload.ServiceClass,
Reason: payload.Reason,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"route_feedback_expire": result})
}
func (m *Module) getFabricServiceChannelRecoveryPolicy(w http.ResponseWriter, r *http.Request) {
item, err := m.service.GetFabricServiceChannelRecoveryPolicy(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"fabric_service_channel_recovery_policy": item})
}
func (m *Module) updateFabricServiceChannelRecoveryPolicy(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
HysteresisPenalty int `json:"hysteresis_penalty"`
PromotionMinSamples int `json:"promotion_min_samples"`
DemotionFailureThreshold int `json:"demotion_failure_threshold"`
DemotionDropThreshold int `json:"demotion_drop_threshold"`
DemotionSlowThreshold int `json:"demotion_slow_threshold"`
DemotionRebuildEnabled *bool `json:"demotion_rebuild_enabled"`
DemotionFencedEnabled *bool `json:"demotion_fenced_enabled"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid recovery policy payload")
return
}
item, err := m.service.UpdateFabricServiceChannelRecoveryPolicy(r.Context(), UpdateFabricServiceChannelRecoveryPolicyInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
HysteresisPenalty: payload.HysteresisPenalty,
PromotionMinSamples: payload.PromotionMinSamples,
DemotionFailureThreshold: payload.DemotionFailureThreshold,
DemotionDropThreshold: payload.DemotionDropThreshold,
DemotionSlowThreshold: payload.DemotionSlowThreshold,
DemotionRebuildEnabled: payload.DemotionRebuildEnabled,
DemotionFencedEnabled: payload.DemotionFencedEnabled,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"fabric_service_channel_recovery_policy": item})
}
func (m *Module) getFabricServiceChannelAdaptivePolicy(w http.ResponseWriter, r *http.Request) {
item, err := m.service.GetFabricServiceChannelAdaptivePolicy(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"fabric_service_channel_adaptive_policy": item})
}
func (m *Module) updateFabricServiceChannelAdaptivePolicy(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
MaxParallelWindow int `json:"max_parallel_window"`
BulkPressureChannelThreshold int `json:"bulk_pressure_channel_threshold"`
QueuePressureHighWatermark int `json:"queue_pressure_high_watermark"`
QueuePressureMaxInFlight int `json:"queue_pressure_max_in_flight"`
ClassWindows map[string]int `json:"class_windows"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid adaptive policy payload")
return
}
item, err := m.service.UpdateFabricServiceChannelAdaptivePolicy(r.Context(), UpdateFabricServiceChannelAdaptivePolicyInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
MaxParallelWindow: payload.MaxParallelWindow,
BulkPressureChannelThreshold: payload.BulkPressureChannelThreshold,
QueuePressureHighWatermark: payload.QueuePressureHighWatermark,
QueuePressureMaxInFlight: payload.QueuePressureMaxInFlight,
ClassWindows: payload.ClassWindows,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"fabric_service_channel_adaptive_policy": item})
}
func (m *Module) getFabricServiceChannelPoolPolicy(w http.ResponseWriter, r *http.Request) {
item, err := m.service.GetFabricServiceChannelPoolPolicy(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"fabric_service_channel_pool_policy": item})
}
func (m *Module) updateFabricServiceChannelPoolPolicy(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
EntryPoolNodeIDs []string `json:"entry_pool_node_ids"`
ExitPoolNodeIDs []string `json:"exit_pool_node_ids"`
PreferredEntryNodeID string `json:"preferred_entry_node_id"`
PreferredExitNodeID string `json:"preferred_exit_node_id"`
SelectionStrategy string `json:"selection_strategy"`
RouteRebuild string `json:"route_rebuild"`
EntryFailover string `json:"entry_failover"`
ExitFailover string `json:"exit_failover"`
BackendFallbackAllowed *bool `json:"backend_fallback_allowed"`
StickySession *bool `json:"sticky_session"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid pool policy payload")
return
}
item, err := m.service.UpdateFabricServiceChannelPoolPolicy(r.Context(), UpdateFabricServiceChannelPoolPolicyInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
EntryPoolNodeIDs: payload.EntryPoolNodeIDs,
ExitPoolNodeIDs: payload.ExitPoolNodeIDs,
PreferredEntryNodeID: payload.PreferredEntryNodeID,
PreferredExitNodeID: payload.PreferredExitNodeID,
SelectionStrategy: payload.SelectionStrategy,
RouteRebuild: payload.RouteRebuild,
EntryFailover: payload.EntryFailover,
ExitFailover: payload.ExitFailover,
BackendFallbackAllowed: payload.BackendFallbackAllowed,
StickySession: payload.StickySession,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"fabric_service_channel_pool_policy": item})
}
func (m *Module) getFabricServiceChannelBreadcrumbWindowPolicy(w http.ResponseWriter, r *http.Request) {
item, err := m.service.GetFabricServiceChannelBreadcrumbWindowPolicy(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"fabric_service_channel_breadcrumb_window_policy": item})
}
func (m *Module) updateFabricServiceChannelBreadcrumbWindowPolicy(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
CurrentWindowSeconds int64 `json:"current_window_seconds"`
HistoryWindowSeconds int64 `json:"history_window_seconds"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid service-channel breadcrumb window policy payload")
return
}
item, err := m.service.UpdateFabricServiceChannelBreadcrumbWindowPolicy(r.Context(), UpdateFabricServiceChannelBreadcrumbWindowPolicyInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
CurrentWindowSeconds: payload.CurrentWindowSeconds,
HistoryWindowSeconds: payload.HistoryWindowSeconds,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"fabric_service_channel_breadcrumb_window_policy": item})
}
func (m *Module) createVPNConnection(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
OrganizationID string `json:"organization_id"`
Name string `json:"name"`
TargetEndpoint json.RawMessage `json:"target_endpoint"`
ProtocolFamily string `json:"protocol_family"`
CredentialRef *string `json:"credential_ref"`
Mode string `json:"mode"`
DesiredState string `json:"desired_state"`
AllowedNodePolicy json.RawMessage `json:"allowed_node_policy"`
RoutingUsage json.RawMessage `json:"routing_usage"`
RoutePolicy json.RawMessage `json:"route_policy"`
QoSPolicy json.RawMessage `json:"qos_policy"`
PlacementPolicy json.RawMessage `json:"placement_policy"`
Metadata json.RawMessage `json:"metadata"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid vpn connection payload")
return
}
item, err := m.service.CreateVPNConnection(r.Context(), CreateVPNConnectionInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
OrganizationID: payload.OrganizationID,
Name: payload.Name,
TargetEndpoint: payload.TargetEndpoint,
ProtocolFamily: payload.ProtocolFamily,
CredentialRef: payload.CredentialRef,
Mode: payload.Mode,
DesiredState: payload.DesiredState,
AllowedNodePolicy: payload.AllowedNodePolicy,
RoutingUsage: payload.RoutingUsage,
RoutePolicy: payload.RoutePolicy,
QoSPolicy: payload.QoSPolicy,
PlacementPolicy: payload.PlacementPolicy,
Metadata: payload.Metadata,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusCreated, map[string]any{"vpn_connection": item})
}
func (m *Module) listVPNConnections(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListVPNConnections(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"vpn_connections": items})
}
func (m *Module) getVPNConnection(w http.ResponseWriter, r *http.Request) {
item, err := m.service.GetVPNConnection(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"), chi.URLParam(r, "vpnConnectionID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"vpn_connection": item})
}
func (m *Module) updateVPNConnectionDesiredState(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
DesiredState string `json:"desired_state"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid vpn connection desired state payload")
return
}
item, err := m.service.UpdateVPNConnectionDesiredState(r.Context(), UpdateVPNConnectionDesiredStateInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
VPNConnectionID: chi.URLParam(r, "vpnConnectionID"),
DesiredState: payload.DesiredState,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"vpn_connection": item})
}
func (m *Module) upsertVPNConnectionRoutePolicy(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
RouteType string `json:"route_type"`
Destination string `json:"destination"`
Action string `json:"action"`
ServiceType *string `json:"service_type"`
Priority int `json:"priority"`
Policy json.RawMessage `json:"policy"`
Status string `json:"status"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid vpn route policy payload")
return
}
item, err := m.service.UpsertVPNConnectionRoutePolicy(r.Context(), UpsertVPNConnectionRoutePolicyInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
VPNConnectionID: chi.URLParam(r, "vpnConnectionID"),
RouteType: payload.RouteType,
Destination: payload.Destination,
Action: payload.Action,
ServiceType: payload.ServiceType,
Priority: payload.Priority,
Policy: payload.Policy,
Status: payload.Status,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusCreated, map[string]any{"route_policy": item})
}
func (m *Module) listVPNConnectionRoutePolicies(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListVPNConnectionRoutePolicies(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"), chi.URLParam(r, "vpnConnectionID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"route_policies": items})
}
func (m *Module) setVPNConnectionAllowedNodes(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
NodeIDs []string `json:"node_ids"`
RolePreference string `json:"role_preference"`
Metadata json.RawMessage `json:"metadata"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid vpn allowed nodes payload")
return
}
items, err := m.service.SetVPNConnectionAllowedNodes(r.Context(), SetVPNConnectionAllowedNodesInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
VPNConnectionID: chi.URLParam(r, "vpnConnectionID"),
NodeIDs: payload.NodeIDs,
RolePreference: payload.RolePreference,
Metadata: payload.Metadata,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"allowed_nodes": items})
}
func (m *Module) listVPNConnectionAllowedNodes(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListVPNConnectionAllowedNodes(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"), chi.URLParam(r, "vpnConnectionID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"allowed_nodes": items})
}
func (m *Module) acquireVPNConnectionLease(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
OwnerNodeID string `json:"owner_node_id"`
TTLSeconds int `json:"ttl_seconds"`
Metadata json.RawMessage `json:"metadata"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid vpn lease acquire payload")
return
}
item, err := m.service.AcquireVPNConnectionLease(r.Context(), AcquireVPNConnectionLeaseInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
VPNConnectionID: chi.URLParam(r, "vpnConnectionID"),
OwnerNodeID: payload.OwnerNodeID,
TTL: time.Duration(payload.TTLSeconds) * time.Second,
Metadata: payload.Metadata,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusCreated, map[string]any{"lease": item})
}
func (m *Module) renewVPNConnectionLease(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
OwnerNodeID string `json:"owner_node_id"`
FencingToken string `json:"fencing_token"`
TTLSeconds int `json:"ttl_seconds"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid vpn lease renew payload")
return
}
item, err := m.service.RenewVPNConnectionLease(r.Context(), RenewVPNConnectionLeaseInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
VPNConnectionID: chi.URLParam(r, "vpnConnectionID"),
LeaseID: chi.URLParam(r, "leaseID"),
OwnerNodeID: payload.OwnerNodeID,
FencingToken: payload.FencingToken,
TTL: time.Duration(payload.TTLSeconds) * time.Second,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"lease": item})
}
func (m *Module) releaseVPNConnectionLease(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
OwnerNodeID string `json:"owner_node_id"`
FencingToken string `json:"fencing_token"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid vpn lease release payload")
return
}
item, err := m.service.ReleaseVPNConnectionLease(r.Context(), ReleaseVPNConnectionLeaseInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
VPNConnectionID: chi.URLParam(r, "vpnConnectionID"),
LeaseID: chi.URLParam(r, "leaseID"),
OwnerNodeID: payload.OwnerNodeID,
FencingToken: payload.FencingToken,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"lease": item})
}
func (m *Module) fenceVPNConnectionLease(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
Reason string `json:"reason"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid vpn lease fence payload")
return
}
item, err := m.service.FenceVPNConnectionLease(r.Context(), FenceVPNConnectionLeaseInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
VPNConnectionID: chi.URLParam(r, "vpnConnectionID"),
LeaseID: chi.URLParam(r, "leaseID"),
Reason: payload.Reason,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"lease": item})
}
func (m *Module) getActiveVPNConnectionLease(w http.ResponseWriter, r *http.Request) {
item, err := m.service.GetActiveVPNConnectionLease(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"), chi.URLParam(r, "vpnConnectionID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"lease": item})
}
func (m *Module) expireStaleVPNConnectionLeases(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid vpn stale lease payload")
return
}
items, err := m.service.ExpireStaleVPNConnectionLeases(r.Context(), ExpireStaleVPNConnectionLeasesInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"expired_leases": items})
}
func (m *Module) listNodeVPNAssignments(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListNodeVPNAssignments(r.Context(), chi.URLParam(r, "clusterID"), chi.URLParam(r, "nodeID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"vpn_assignments": items})
}
func (m *Module) renewNodeVPNAssignmentLease(w http.ResponseWriter, r *http.Request) {
var payload struct {
TTLSeconds int `json:"ttl_seconds"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid vpn node lease renew payload")
return
}
item, err := m.service.RenewNodeVPNAssignmentLease(r.Context(), RenewNodeVPNAssignmentLeaseInput{
ClusterID: chi.URLParam(r, "clusterID"),
VPNConnectionID: chi.URLParam(r, "vpnConnectionID"),
LeaseID: chi.URLParam(r, "leaseID"),
OwnerNodeID: chi.URLParam(r, "nodeID"),
TTL: time.Duration(payload.TTLSeconds) * time.Second,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"lease": NodeVPNAssignmentLease{
LeaseID: item.ID,
OwnerNodeID: item.OwnerNodeID,
LeaseGeneration: item.LeaseGeneration,
Status: item.Status,
RenewedAt: item.RenewedAt,
ExpiresAt: item.ExpiresAt,
}})
}
func (m *Module) reportNodeVPNAssignmentStatus(w http.ResponseWriter, r *http.Request) {
var payload struct {
ObservedStatus string `json:"observed_status"`
StatusPayload json.RawMessage `json:"status_payload"`
ObservedAt time.Time `json:"observed_at"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid vpn assignment status payload")
return
}
item, err := m.service.ReportNodeVPNAssignmentStatus(r.Context(), ReportNodeVPNAssignmentStatusInput{
ClusterID: chi.URLParam(r, "clusterID"),
NodeID: chi.URLParam(r, "nodeID"),
VPNConnectionID: chi.URLParam(r, "vpnConnectionID"),
ObservedStatus: payload.ObservedStatus,
StatusPayload: payload.StatusPayload,
ObservedAt: payload.ObservedAt,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusAccepted, map[string]any{"vpn_assignment_status": item})
}
func (m *Module) getVPNClientProfile(w http.ResponseWriter, r *http.Request) {
preferredEntryNodeID := strings.TrimSpace(r.URL.Query().Get("entry_node_id"))
if preferredEntryNodeID == "" {
preferredEntryNodeID = strings.TrimSpace(r.Header.Get("X-RAP-Entry-Node"))
}
preferredExitNodeID := strings.TrimSpace(r.URL.Query().Get("exit_node_id"))
if preferredExitNodeID == "" {
preferredExitNodeID = strings.TrimSpace(r.Header.Get("X-RAP-Exit-Node"))
}
item, err := m.service.GetVPNClientProfile(
r.Context(),
chi.URLParam(r, "clusterID"),
r.URL.Query().Get("organization_id"),
r.URL.Query().Get("user_id"),
preferredEntryNodeID,
preferredExitNodeID,
)
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"vpn_client_profile": item})
}
func (m *Module) getVPNPacketStats(w http.ResponseWriter, r *http.Request) {
httpx.WriteJSON(w, http.StatusOK, map[string]any{
"vpn_packet_stats": m.vpnPacketHub.Snapshot(
chi.URLParam(r, "clusterID"),
chi.URLParam(r, "vpnConnectionID"),
),
})
}
func (m *Module) reportVPNClientDiagnosticStatus(w http.ResponseWriter, r *http.Request) {
var payload map[string]any
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid vpn client diagnostic status payload")
return
}
item := m.vpnClientDiagnosticHub.Report(
chi.URLParam(r, "clusterID"),
chi.URLParam(r, "deviceID"),
payload,
)
httpx.WriteJSON(w, http.StatusAccepted, map[string]any{"vpn_client_diagnostic_status": item})
}
func (m *Module) listVPNClientDiagnosticStatuses(w http.ResponseWriter, r *http.Request) {
items := m.vpnClientDiagnosticHub.List(chi.URLParam(r, "clusterID"))
httpx.WriteJSON(w, http.StatusOK, map[string]any{"vpn_client_diagnostic_statuses": items})
}
func (m *Module) getVPNClientDiagnosticStatus(w http.ResponseWriter, r *http.Request) {
item, ok := m.vpnClientDiagnosticHub.Status(
chi.URLParam(r, "clusterID"),
chi.URLParam(r, "deviceID"),
)
if !ok {
httpx.WriteError(w, http.StatusNotFound, "vpn client diagnostic status not found")
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"vpn_client_diagnostic_status": item})
}
func (m *Module) enqueueVPNClientDiagnosticCommand(w http.ResponseWriter, r *http.Request) {
var payload map[string]any
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid vpn client diagnostic command payload")
return
}
commandType, _ := payload["type"].(string)
if strings.TrimSpace(commandType) == "" {
httpx.WriteError(w, http.StatusBadRequest, "vpn client diagnostic command type is required")
return
}
item := m.vpnClientDiagnosticHub.Enqueue(
chi.URLParam(r, "clusterID"),
chi.URLParam(r, "deviceID"),
payload,
)
httpx.WriteJSON(w, http.StatusAccepted, map[string]any{"vpn_client_diagnostic_command": item})
}
func (m *Module) getVPNClientDiagnosticCommand(w http.ResponseWriter, r *http.Request) {
timeout := 25 * time.Second
if raw := r.URL.Query().Get("timeout_ms"); raw != "" {
if parsed, err := strconv.Atoi(raw); err == nil && parsed >= 0 && parsed <= 30000 {
timeout = time.Duration(parsed) * time.Millisecond
}
}
item, ok := m.vpnClientDiagnosticHub.Pop(
r.Context(),
chi.URLParam(r, "clusterID"),
chi.URLParam(r, "deviceID"),
timeout,
)
if !ok {
w.WriteHeader(http.StatusNoContent)
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"vpn_client_diagnostic_command": item})
}
func (m *Module) postVPNClientPacket(w http.ResponseWriter, r *http.Request) {
m.handleVPNPacketPost(w, r, vpnDirectionClientToGateway)
}
func (m *Module) getVPNClientPacket(w http.ResponseWriter, r *http.Request) {
m.handleVPNPacketGet(w, r, vpnDirectionGatewayToClient)
}
func (m *Module) postVPNGatewayPacket(w http.ResponseWriter, r *http.Request) {
m.handleVPNPacketPost(w, r, vpnDirectionGatewayToClient)
}
func (m *Module) getVPNGatewayPacket(w http.ResponseWriter, r *http.Request) {
m.handleVPNPacketGet(w, r, vpnDirectionClientToGateway)
}
func (m *Module) resetVPNPacketQueues(w http.ResponseWriter, r *http.Request) {
clusterID := chi.URLParam(r, "clusterID")
vpnConnectionID := chi.URLParam(r, "vpnConnectionID")
clientToGateway := m.vpnPacketHub.Clear(vpnPacketKey{
ClusterID: clusterID,
VPNConnectionID: vpnConnectionID,
Direction: vpnDirectionClientToGateway,
})
gatewayToClient := m.vpnPacketHub.Clear(vpnPacketKey{
ClusterID: clusterID,
VPNConnectionID: vpnConnectionID,
Direction: vpnDirectionGatewayToClient,
})
httpx.WriteJSON(w, http.StatusOK, map[string]any{
"vpn_packet_queues_reset": map[string]any{
"client_to_gateway": clientToGateway,
"gateway_to_client": gatewayToClient,
},
})
}
func (m *Module) handleVPNPacketPost(w http.ResponseWriter, r *http.Request, direction string) {
maxBodyBytes := int64(vpnPacketMaxBytes)
if r.URL.Query().Get("batch") == "true" {
maxBodyBytes = int64(vpnPacketBatchMaxBytes)
}
body, err := io.ReadAll(http.MaxBytesReader(w, r.Body, maxBodyBytes))
if err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid vpn packet payload")
return
}
if len(body) == 0 {
httpx.WriteError(w, http.StatusBadRequest, "empty vpn packet payload")
return
}
key := vpnPacketKey{
ClusterID: chi.URLParam(r, "clusterID"),
VPNConnectionID: chi.URLParam(r, "vpnConnectionID"),
Direction: direction,
}
if r.URL.Query().Get("batch") == "true" {
packets, err := decodeVPNPacketBatch(body)
if err != nil {
httpx.WriteError(w, http.StatusBadRequest, err.Error())
return
}
for _, packet := range packets {
if err := m.vpnPacketHub.Push(key, packet); err != nil {
httpx.WriteError(w, http.StatusServiceUnavailable, err.Error())
return
}
}
w.WriteHeader(http.StatusAccepted)
return
}
if err := m.vpnPacketHub.Push(key, body); err != nil {
httpx.WriteError(w, http.StatusServiceUnavailable, err.Error())
return
}
w.WriteHeader(http.StatusAccepted)
}
func (m *Module) handleVPNPacketGet(w http.ResponseWriter, r *http.Request, direction string) {
timeout := 25 * time.Second
if raw := r.URL.Query().Get("timeout_ms"); raw != "" {
if parsed, err := strconv.Atoi(raw); err == nil && parsed >= 0 && parsed <= 30000 {
timeout = time.Duration(parsed) * time.Millisecond
}
}
key := vpnPacketKey{
ClusterID: chi.URLParam(r, "clusterID"),
VPNConnectionID: chi.URLParam(r, "vpnConnectionID"),
Direction: direction,
}
if r.URL.Query().Get("batch") == "true" {
packets := m.vpnPacketHub.PopBatch(r.Context(), key, timeout, vpnPacketBatchMaxPackets, vpnPacketBatchMaxBytes)
if len(packets) == 0 {
w.WriteHeader(http.StatusNoContent)
return
}
w.Header().Set("Content-Type", "application/vnd.rap.vpn-packet-batch.v1")
w.WriteHeader(http.StatusOK)
_, _ = w.Write(encodeVPNPacketBatch(packets))
return
}
packet, ok := m.vpnPacketHub.Pop(r.Context(), key, timeout)
if !ok {
w.WriteHeader(http.StatusNoContent)
return
}
w.Header().Set("Content-Type", "application/octet-stream")
w.WriteHeader(http.StatusOK)
_, _ = w.Write(packet)
}
const (
vpnDirectionClientToGateway = "client_to_gateway"
vpnDirectionGatewayToClient = "gateway_to_client"
vpnPacketMaxBytes = 65535
vpnPacketFlowShardCount = 16
vpnPacketFlowShardDepth = 4096
vpnPacketQueueDepth = vpnPacketFlowShardCount * vpnPacketFlowShardDepth
vpnPacketBatchMaxPackets = 1024
vpnPacketBatchMaxBytes = 4 * 1024 * 1024
vpnPacketBatchGatherTimeout = 3 * time.Millisecond
vpnPacketStatsWindow = 5 * time.Second
)
type vpnPacketKey struct {
ClusterID string
VPNConnectionID string
Direction string
}
type vpnPacketHub struct {
queuesMu sync.RWMutex
statsMu sync.Mutex
queues map[vpnPacketKey]*vpnPacketQueue
stats map[vpnPacketKey]vpnPacketStats
}
type vpnPacketQueue struct {
shards []chan []byte
popMu sync.Mutex
popCursor int
}
type vpnPacketStats struct {
Pushed uint64
Popped uint64
Dropped uint64
QueueFullDrops uint64
RequeueDrops uint64
ClearedStale uint64
PushedBytes uint64
PoppedBytes uint64
WindowStartedAt time.Time
WindowPushed uint64
WindowPopped uint64
WindowPushedBytes uint64
WindowPoppedBytes uint64
QueueDepthHigh int
QueueDepthHighAt time.Time
ShardDepthHigh int
ShardDepthHighAt time.Time
LastPushSize int
LastPopSize int
LastPushAt time.Time
LastPopAt time.Time
LastPushSummary string
LastPopSummary string
Recent []vpnPacketTrace
}
type vpnPacketTrace struct {
Event string `json:"event"`
Summary string `json:"summary"`
Size int `json:"size"`
CreatedAt time.Time `json:"created_at"`
}
func newVPNPacketHub() *vpnPacketHub {
return &vpnPacketHub{
queues: map[vpnPacketKey]*vpnPacketQueue{},
stats: map[vpnPacketKey]vpnPacketStats{},
}
}
func (h *vpnPacketHub) Push(key vpnPacketKey, packet []byte) error {
queue := h.queue(key)
if queue.push(packet) {
_, queueDepth, shardDepth := queue.depths()
h.recordPush(key, packet, time.Now().UTC(), queueDepth, shardDepth)
return nil
}
h.recordQueueFullDrop(key, packet, time.Now().UTC())
return fmt.Errorf("vpn packet queue is full")
}
func (h *vpnPacketHub) Pop(ctx context.Context, key vpnPacketKey, timeout time.Duration) ([]byte, bool) {
queue := h.queue(key)
if packet, ok := queue.pop(ctx, timeout); ok {
h.recordPop(key, packet, time.Now().UTC())
return packet, true
}
return nil, false
}
func (h *vpnPacketHub) PopBatch(ctx context.Context, key vpnPacketKey, timeout time.Duration, maxPackets, maxBytes int) [][]byte {
if maxPackets <= 0 {
maxPackets = 1
}
if maxBytes <= 0 {
maxBytes = vpnPacketMaxBytes
}
first, ok := h.Pop(ctx, key, timeout)
if !ok {
return nil
}
packets := [][]byte{first}
total := len(first)
gatherUntil := time.Now().Add(vpnPacketBatchGatherTimeout)
queue := h.queue(key)
for len(packets) < maxPackets && total < maxBytes {
packet, ok := queue.pop(ctx, 0)
if !ok {
remaining := time.Until(gatherUntil)
if remaining <= 0 {
return packets
}
packet, ok = queue.pop(ctx, remaining)
if !ok {
return packets
}
}
if total+len(packet)+4 > maxBytes {
if !queue.requeue(packet) {
h.recordRequeueDrop(key, packet, time.Now().UTC())
}
return packets
}
h.recordPop(key, packet, time.Now().UTC())
packets = append(packets, packet)
total += len(packet) + 4
}
return packets
}
func (h *vpnPacketHub) addWindowSamplesLocked(
stats *vpnPacketStats,
now time.Time,
pushedPackets, poppedPackets,
pushedBytes, poppedBytes uint64,
) {
if stats.WindowStartedAt.IsZero() || now.Sub(stats.WindowStartedAt) >= vpnPacketStatsWindow {
stats.WindowStartedAt = now
stats.WindowPushed = 0
stats.WindowPopped = 0
stats.WindowPushedBytes = 0
stats.WindowPoppedBytes = 0
}
stats.WindowPushed += pushedPackets
stats.WindowPopped += poppedPackets
stats.WindowPushedBytes += pushedBytes
stats.WindowPoppedBytes += poppedBytes
}
func computeVPNRateStats(now time.Time, startedAt time.Time, packets, bytes uint64) (float64, float64) {
if packets == 0 || bytes == 0 || startedAt.IsZero() {
return 0, 0
}
elapsed := now.Sub(startedAt).Seconds()
if elapsed <= 0 {
return 0, 0
}
pps := float64(packets) / elapsed
mbps := float64(bytes*8) / (elapsed * 1_000_000)
return pps, mbps
}
func (h *vpnPacketHub) appendRateStats(metrics map[string]any, now time.Time, stats vpnPacketStats) map[string]any {
pushPps, pushMbps := computeVPNRateStats(now, stats.WindowStartedAt, stats.WindowPushed, stats.WindowPushedBytes)
popPps, popMbps := computeVPNRateStats(now, stats.WindowStartedAt, stats.WindowPopped, stats.WindowPoppedBytes)
metrics["rate_window_seconds"] = vpnPacketStatsWindow.Seconds()
metrics["window_push_rate_pps"] = pushPps
metrics["window_push_rate_mbps"] = pushMbps
metrics["window_pop_rate_pps"] = popPps
metrics["window_pop_rate_mbps"] = popMbps
metrics["window_push_packets"] = stats.WindowPushed
metrics["window_pop_packets"] = stats.WindowPopped
metrics["window_push_bytes"] = stats.WindowPushedBytes
metrics["window_pop_bytes"] = stats.WindowPoppedBytes
return metrics
}
func (h *vpnPacketHub) Clear(key vpnPacketKey) int {
queue := h.queue(key)
cleared := queue.clear()
h.recordClear(key, cleared)
return cleared
}
func encodeVPNPacketBatch(packets [][]byte) []byte {
total := 0
for _, packet := range packets {
total += 4 + len(packet)
}
out := make([]byte, total)
offset := 0
for _, packet := range packets {
binary.BigEndian.PutUint32(out[offset:offset+4], uint32(len(packet)))
offset += 4
copy(out[offset:offset+len(packet)], packet)
offset += len(packet)
}
return out
}
func decodeVPNPacketBatch(payload []byte) ([][]byte, error) {
var packets [][]byte
for offset := 0; offset < len(payload); {
if offset+4 > len(payload) {
return nil, fmt.Errorf("truncated vpn packet batch header")
}
size := int(binary.BigEndian.Uint32(payload[offset : offset+4]))
offset += 4
if size <= 0 || size > vpnPacketMaxBytes {
return nil, fmt.Errorf("invalid vpn packet batch item size")
}
if offset+size > len(payload) {
return nil, fmt.Errorf("truncated vpn packet batch item")
}
packets = append(packets, append([]byte(nil), payload[offset:offset+size]...))
offset += size
}
if len(packets) == 0 {
return nil, fmt.Errorf("empty vpn packet batch")
}
return packets, nil
}
func (h *vpnPacketHub) queue(key vpnPacketKey) *vpnPacketQueue {
h.queuesMu.RLock()
queue := h.queues[key]
h.queuesMu.RUnlock()
if queue != nil {
return queue
}
h.queuesMu.Lock()
defer h.queuesMu.Unlock()
queue = h.queues[key]
if queue != nil {
return queue
}
queue = newVPNPacketQueue()
h.queues[key] = queue
return queue
}
func newVPNPacketQueue() *vpnPacketQueue {
shardCount := vpnPacketFlowShardCount
if shardCount <= 0 {
shardCount = 1
}
shardDepth := vpnPacketFlowShardDepth
if shardDepth <= 0 {
shardDepth = 1
}
queue := &vpnPacketQueue{
shards: make([]chan []byte, shardCount),
}
for i := range queue.shards {
queue.shards[i] = make(chan []byte, shardDepth)
}
return queue
}
func (q *vpnPacketQueue) push(packet []byte) bool {
return q.enqueue(append([]byte(nil), packet...))
}
func (q *vpnPacketQueue) requeue(packet []byte) bool {
return q.enqueue(packet)
}
func (q *vpnPacketQueue) enqueue(packet []byte) bool {
if len(q.shards) == 0 {
return false
}
shard := vpnPacketFlowShard(packet, len(q.shards))
select {
case q.shards[shard] <- packet:
return true
default:
return false
}
}
func (q *vpnPacketQueue) pop(ctx context.Context, timeout time.Duration) ([]byte, bool) {
q.popMu.Lock()
defer q.popMu.Unlock()
if packet, ok := q.popNonBlockingLocked(); ok {
return packet, true
}
if timeout <= 0 || len(q.shards) == 0 {
return nil, false
}
timer := time.NewTimer(timeout)
defer timer.Stop()
cases := make([]reflect.SelectCase, 0, len(q.shards)+2)
cases = append(cases,
reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(ctx.Done())},
reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(timer.C)},
)
start := q.popCursor % len(q.shards)
for i := range q.shards {
cases = append(cases, reflect.SelectCase{
Dir: reflect.SelectRecv,
Chan: reflect.ValueOf(q.shards[(start+i)%len(q.shards)]),
})
}
chosen, value, ok := reflect.Select(cases)
if chosen < 2 || !ok {
return nil, false
}
shard := (start + chosen - 2) % len(q.shards)
q.popCursor = (shard + 1) % len(q.shards)
packet, ok := value.Interface().([]byte)
return packet, ok
}
func (q *vpnPacketQueue) popNonBlockingLocked() ([]byte, bool) {
if len(q.shards) == 0 {
return nil, false
}
start := q.popCursor % len(q.shards)
for i := range q.shards {
shard := (start + i) % len(q.shards)
select {
case packet := <-q.shards[shard]:
q.popCursor = (shard + 1) % len(q.shards)
return packet, true
default:
}
}
return nil, false
}
func (q *vpnPacketQueue) clear() int {
cleared := 0
for _, shard := range q.shards {
for {
select {
case <-shard:
cleared++
default:
goto nextShard
}
}
nextShard:
}
return cleared
}
func (q *vpnPacketQueue) depths() ([]int, int, int) {
depths := make([]int, len(q.shards))
total := 0
maxDepth := 0
for i, shard := range q.shards {
depth := len(shard)
depths[i] = depth
total += depth
if depth > maxDepth {
maxDepth = depth
}
}
return depths, total, maxDepth
}
func (h *vpnPacketHub) Snapshot(clusterID, vpnConnectionID string) map[string]any {
now := time.Now().UTC()
out := map[string]any{}
for _, direction := range []string{vpnDirectionClientToGateway, vpnDirectionGatewayToClient} {
key := vpnPacketKey{ClusterID: clusterID, VPNConnectionID: vpnConnectionID, Direction: direction}
h.statsMu.Lock()
stats := h.stats[key]
h.statsMu.Unlock()
queueDepth := 0
queueDepthMax := 0
queueDepths := []int{}
h.queuesMu.RLock()
queue := h.queues[key]
h.queuesMu.RUnlock()
if queue != nil {
queueDepths, queueDepth, queueDepthMax = queue.depths()
}
metric := map[string]any{
"pushed": stats.Pushed,
"pushed_bytes": stats.PushedBytes,
"popped": stats.Popped,
"popped_bytes": stats.PoppedBytes,
"dropped": stats.Dropped,
"queue_full_drops": stats.QueueFullDrops,
"requeue_drops": stats.RequeueDrops,
"cleared_stale_packets": stats.ClearedStale,
"queue_depth": queueDepth,
"queue_depths": queueDepths,
"queue_depth_max": queueDepthMax,
"queue_depth_high_watermark": stats.QueueDepthHigh,
"queue_depth_high_at": stats.QueueDepthHighAt,
"shard_depth_high_watermark": stats.ShardDepthHigh,
"shard_depth_high_at": stats.ShardDepthHighAt,
"queue_capacity": vpnPacketQueueDepth,
"queue_shard_capacity": vpnPacketFlowShardDepth,
"flow_shard_count": len(queueDepths),
"flow_isolation": "ipv4_5tuple_sharded_round_robin",
"last_push_size": stats.LastPushSize,
"last_pop_size": stats.LastPopSize,
"last_push_at": stats.LastPushAt,
"last_pop_at": stats.LastPopAt,
"last_push": stats.LastPushSummary,
"last_pop": stats.LastPopSummary,
"recent": stats.Recent,
}
h.appendRateStats(metric, now, stats)
out[direction] = metric
}
return out
}
func (h *vpnPacketHub) recordPush(key vpnPacketKey, packet []byte, now time.Time, queueDepth, shardDepth int) {
if !h.statsMu.TryLock() {
return
}
defer h.statsMu.Unlock()
stats := h.stats[key]
stats.Pushed++
stats.PushedBytes += uint64(len(packet))
stats.LastPushSize = len(packet)
stats.LastPushAt = now
stats.LastPushSummary = vpnPacketSummary(packet)
if queueDepth > stats.QueueDepthHigh {
stats.QueueDepthHigh = queueDepth
stats.QueueDepthHighAt = now
}
if shardDepth > stats.ShardDepthHigh {
stats.ShardDepthHigh = shardDepth
stats.ShardDepthHighAt = now
}
h.addWindowSamplesLocked(&stats, now, 1, 0, uint64(len(packet)), 0)
stats.Recent = appendVPNPacketTrace(stats.Recent, "push", packet, stats.LastPushAt)
h.stats[key] = stats
}
func (h *vpnPacketHub) recordPop(key vpnPacketKey, packet []byte, now time.Time) {
if !h.statsMu.TryLock() {
return
}
defer h.statsMu.Unlock()
stats := h.stats[key]
stats.Popped++
stats.PoppedBytes += uint64(len(packet))
stats.LastPopSize = len(packet)
stats.LastPopAt = now
stats.LastPopSummary = vpnPacketSummary(packet)
h.addWindowSamplesLocked(&stats, now, 0, 1, 0, uint64(len(packet)))
stats.Recent = appendVPNPacketTrace(stats.Recent, "pop", packet, stats.LastPopAt)
h.stats[key] = stats
}
func (h *vpnPacketHub) recordQueueFullDrop(key vpnPacketKey, packet []byte, now time.Time) {
if !h.statsMu.TryLock() {
return
}
defer h.statsMu.Unlock()
stats := h.stats[key]
stats.Dropped++
stats.QueueFullDrops++
stats.Recent = appendVPNPacketTrace(stats.Recent, "drop_queue_full", packet, now)
h.stats[key] = stats
}
func (h *vpnPacketHub) recordRequeueDrop(key vpnPacketKey, packet []byte, now time.Time) {
if !h.statsMu.TryLock() {
return
}
defer h.statsMu.Unlock()
stats := h.stats[key]
stats.Dropped++
stats.RequeueDrops++
stats.Recent = appendVPNPacketTrace(stats.Recent, "drop_requeue_full", packet, now)
h.stats[key] = stats
}
func (h *vpnPacketHub) recordClear(key vpnPacketKey, cleared int) {
if cleared <= 0 {
return
}
h.statsMu.Lock()
defer h.statsMu.Unlock()
now := time.Now().UTC()
stats := h.stats[key]
stats.ClearedStale += uint64(cleared)
stats.Recent = append(stats.Recent, vpnPacketTrace{
Event: "clear",
Summary: fmt.Sprintf("cleared stale packets=%d", cleared),
Size: cleared,
CreatedAt: now,
})
const maxRecentVPNPacketTrace = 24
if len(stats.Recent) > maxRecentVPNPacketTrace {
stats.Recent = stats.Recent[len(stats.Recent)-maxRecentVPNPacketTrace:]
}
h.stats[key] = stats
}
func appendVPNPacketTrace(recent []vpnPacketTrace, event string, packet []byte, at time.Time) []vpnPacketTrace {
recent = append(recent, vpnPacketTrace{
Event: event,
Summary: vpnPacketSummary(packet),
Size: len(packet),
CreatedAt: at,
})
const maxRecentVPNPacketTrace = 24
if len(recent) > maxRecentVPNPacketTrace {
recent = recent[len(recent)-maxRecentVPNPacketTrace:]
}
return recent
}
func vpnPacketFlowShard(packet []byte, shardCount int) int {
if shardCount <= 1 {
return 0
}
if len(packet) < 20 {
return len(packet) % shardCount
}
version := (packet[0] >> 4) & 0x0f
if version != 4 {
return len(packet) % shardCount
}
ihl := int(packet[0]&0x0f) * 4
if ihl < 20 || len(packet) < ihl {
return len(packet) % shardCount
}
proto := uint32(packet[9])
srcIP := vpnIPv4Uint32(packet[12:16])
dstIP := vpnIPv4Uint32(packet[16:20])
srcPort := uint32(0)
dstPort := uint32(0)
if (proto == 6 || proto == 17) && len(packet) >= ihl+4 {
srcPort = uint32(vpnU16(packet[ihl : ihl+2]))
dstPort = uint32(vpnU16(packet[ihl+2 : ihl+4]))
} else if proto == 1 && len(packet) >= ihl+2 {
srcPort = uint32(packet[ihl])
dstPort = uint32(packet[ihl+1])
}
hash := srcIP ^ vpnRotateLeft32(dstIP, 7) ^ (proto << 24) ^ (srcPort << 11) ^ dstPort
hash ^= hash >> 16
hash *= 0x7feb352d
hash ^= hash >> 15
return int(hash % uint32(shardCount))
}
func vpnIPv4Uint32(raw []byte) uint32 {
if len(raw) < 4 {
return 0
}
return uint32(raw[0])<<24 | uint32(raw[1])<<16 | uint32(raw[2])<<8 | uint32(raw[3])
}
func vpnRotateLeft32(value uint32, shift uint) uint32 {
shift %= 32
if shift == 0 {
return value
}
return (value << shift) | (value >> (32 - shift))
}
func vpnPacketSummary(packet []byte) string {
if len(packet) < 20 {
return fmt.Sprintf("size=%d", len(packet))
}
version := (packet[0] >> 4) & 0x0f
if version != 4 {
return fmt.Sprintf("size=%d ip_version=%d", len(packet), version)
}
ihl := int(packet[0]&0x0f) * 4
if ihl < 20 || len(packet) < ihl {
return fmt.Sprintf("size=%d ipv4=truncated", len(packet))
}
proto := int(packet[9])
base := fmt.Sprintf("size=%d %s -> %s proto=%d", len(packet), vpnIPv4(packet[12:16]), vpnIPv4(packet[16:20]), proto)
if (proto == 6 || proto == 17) && len(packet) >= ihl+4 {
base += fmt.Sprintf(" %d->%d", vpnU16(packet[ihl:ihl+2]), vpnU16(packet[ihl+2:ihl+4]))
if proto == 6 && len(packet) >= ihl+14 {
base += " flags=" + vpnTCPFlags(packet[ihl+13])
}
} else if proto == 1 && len(packet) >= ihl+2 {
base += fmt.Sprintf(" icmp_type=%d icmp_code=%d", packet[ihl], packet[ihl+1])
}
return base
}
func vpnIPv4(raw []byte) string {
if len(raw) < 4 {
return "0.0.0.0"
}
return fmt.Sprintf("%d.%d.%d.%d", raw[0], raw[1], raw[2], raw[3])
}
func vpnU16(raw []byte) int {
if len(raw) < 2 {
return 0
}
return int(raw[0])<<8 | int(raw[1])
}
func vpnTCPFlags(flags byte) string {
out := strings.Builder{}
if flags&0x02 != 0 {
out.WriteByte('S')
}
if flags&0x10 != 0 {
out.WriteByte('A')
}
if flags&0x01 != 0 {
out.WriteByte('F')
}
if flags&0x04 != 0 {
out.WriteByte('R')
}
if flags&0x08 != 0 {
out.WriteByte('P')
}
if out.Len() == 0 {
return fmt.Sprintf("%d", flags)
}
return out.String()
}
type vpnClientDiagnosticKey struct {
ClusterID string
DeviceID string
}
type vpnClientDiagnosticStatus struct {
ClusterID string `json:"cluster_id"`
DeviceID string `json:"device_id"`
Payload map[string]any `json:"payload"`
ObservedAt time.Time `json:"observed_at"`
}
type vpnClientDiagnosticCommand struct {
ID string `json:"id"`
ClusterID string `json:"cluster_id"`
DeviceID string `json:"device_id"`
Payload map[string]any `json:"payload"`
CreatedAt time.Time `json:"created_at"`
}
type vpnClientDiagnosticHub struct {
mu sync.Mutex
statuses map[vpnClientDiagnosticKey]vpnClientDiagnosticStatus
queues map[vpnClientDiagnosticKey]chan vpnClientDiagnosticCommand
}
func newVPNClientDiagnosticHub() *vpnClientDiagnosticHub {
return &vpnClientDiagnosticHub{
statuses: map[vpnClientDiagnosticKey]vpnClientDiagnosticStatus{},
queues: map[vpnClientDiagnosticKey]chan vpnClientDiagnosticCommand{},
}
}
func (h *vpnClientDiagnosticHub) Report(clusterID, deviceID string, payload map[string]any) vpnClientDiagnosticStatus {
key := vpnClientDiagnosticKey{ClusterID: clusterID, DeviceID: deviceID}
item := vpnClientDiagnosticStatus{
ClusterID: clusterID,
DeviceID: deviceID,
Payload: cloneDiagnosticPayload(payload),
ObservedAt: time.Now().UTC(),
}
h.mu.Lock()
h.statuses[key] = item
h.mu.Unlock()
return item
}
func (h *vpnClientDiagnosticHub) Status(clusterID, deviceID string) (vpnClientDiagnosticStatus, bool) {
key := vpnClientDiagnosticKey{ClusterID: clusterID, DeviceID: deviceID}
h.mu.Lock()
defer h.mu.Unlock()
item, ok := h.statuses[key]
return item, ok
}
func (h *vpnClientDiagnosticHub) List(clusterID string) []vpnClientDiagnosticStatus {
h.mu.Lock()
defer h.mu.Unlock()
out := make([]vpnClientDiagnosticStatus, 0)
for key, item := range h.statuses {
if key.ClusterID == clusterID {
out = append(out, item)
}
}
sort.Slice(out, func(i, j int) bool {
return out[i].ObservedAt.After(out[j].ObservedAt)
})
return out
}
func (h *vpnClientDiagnosticHub) Enqueue(clusterID, deviceID string, payload map[string]any) vpnClientDiagnosticCommand {
item := vpnClientDiagnosticCommand{
ID: fmt.Sprintf("vpn_diag_%d", time.Now().UnixNano()),
ClusterID: clusterID,
DeviceID: deviceID,
Payload: cloneDiagnosticPayload(payload),
CreatedAt: time.Now().UTC(),
}
queue := h.queue(vpnClientDiagnosticKey{ClusterID: clusterID, DeviceID: deviceID})
if vpnClientDiagnosticCommandIsPriorityStop(payload) {
drainVPNClientDiagnosticQueue(queue)
}
select {
case queue <- item:
default:
select {
case <-queue:
default:
}
queue <- item
}
return item
}
func vpnClientDiagnosticCommandIsPriorityStop(payload map[string]any) bool {
commandType, _ := payload["type"].(string)
return strings.TrimSpace(commandType) == "stop_vpn"
}
func drainVPNClientDiagnosticQueue(queue chan vpnClientDiagnosticCommand) {
for {
select {
case <-queue:
default:
return
}
}
}
func (h *vpnClientDiagnosticHub) Pop(ctx context.Context, clusterID, deviceID string, timeout time.Duration) (vpnClientDiagnosticCommand, bool) {
queue := h.queue(vpnClientDiagnosticKey{ClusterID: clusterID, DeviceID: deviceID})
if timeout <= 0 {
select {
case item := <-queue:
return item, true
default:
return vpnClientDiagnosticCommand{}, false
}
}
timer := time.NewTimer(timeout)
defer timer.Stop()
select {
case item := <-queue:
return item, true
case <-timer.C:
return vpnClientDiagnosticCommand{}, false
case <-ctx.Done():
return vpnClientDiagnosticCommand{}, false
}
}
func (h *vpnClientDiagnosticHub) queue(key vpnClientDiagnosticKey) chan vpnClientDiagnosticCommand {
h.mu.Lock()
defer h.mu.Unlock()
queue := h.queues[key]
if queue == nil {
queue = make(chan vpnClientDiagnosticCommand, 32)
h.queues[key] = queue
}
return queue
}
func cloneDiagnosticPayload(payload map[string]any) map[string]any {
out := map[string]any{}
for key, value := range payload {
out[key] = value
}
return out
}
func (m *Module) getClusterAuthority(w http.ResponseWriter, r *http.Request) {
item, err := m.service.GetClusterAuthorityState(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"authority_state": item})
}
func (m *Module) updateClusterAuthority(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
AuthorityState string `json:"authority_state"`
MutationMode string `json:"mutation_mode"`
Notes *string `json:"notes"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid cluster authority payload")
return
}
item, err := m.service.UpdateClusterAuthorityState(r.Context(), UpdateClusterAuthorityInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
AuthorityState: payload.AuthorityState,
MutationMode: payload.MutationMode,
Notes: payload.Notes,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"authority_state": item})
}
func (m *Module) listClusterAdminSummaries(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListClusterAdminSummaries(r.Context(), r.URL.Query().Get("actor_user_id"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"cluster_summaries": items})
}
func (m *Module) listFabricTestingFlags(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListFabricTestingFlags(r.Context(), r.URL.Query().Get("actor_user_id"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"testing_flags": items})
}
func (m *Module) upsertFabricTestingFlag(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
ScopeType string `json:"scope_type"`
ScopeID *string `json:"scope_id"`
ClusterID *string `json:"cluster_id"`
Enabled bool `json:"enabled"`
TelemetryEnabled bool `json:"telemetry_enabled"`
SyntheticLinksEnabled bool `json:"synthetic_links_enabled"`
HistoryRetentionHours int `json:"history_retention_hours"`
Metadata json.RawMessage `json:"metadata"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid testing flag payload")
return
}
item, err := m.service.UpsertFabricTestingFlag(r.Context(), UpsertFabricTestingFlagInput{
ActorUserID: payload.ActorUserID,
ScopeType: payload.ScopeType,
ScopeID: payload.ScopeID,
ClusterID: payload.ClusterID,
Enabled: payload.Enabled,
TelemetryEnabled: payload.TelemetryEnabled,
SyntheticLinksEnabled: payload.SyntheticLinksEnabled,
HistoryRetentionHours: payload.HistoryRetentionHours,
Metadata: payload.Metadata,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"testing_flag": item})
}
func (m *Module) listAuditEvents(w http.ResponseWriter, r *http.Request) {
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
items, err := m.service.ListAuditEvents(r.Context(), r.URL.Query().Get("actor_user_id"), ListAuditEventsInput{
ClusterID: chi.URLParam(r, "clusterID"),
EventTypes: queryStringList(r, "event_type"),
TargetTypes: queryStringList(r, "target_type"),
Correlation: r.URL.Query().Get("correlation"),
Limit: limit,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{
"audit_events": items,
"audit_summary": summarizeClusterAuditEvents(items),
})
}
func queryStringList(r *http.Request, key string) []string {
values := []string{}
for _, raw := range r.URL.Query()[key] {
for _, part := range strings.Split(raw, ",") {
if value := strings.TrimSpace(part); value != "" {
values = append(values, value)
}
}
}
return values
}
func (m *Module) streamClusterEvents(w http.ResponseWriter, r *http.Request) {
flusher, ok := w.(http.Flusher)
if !ok {
httpx.WriteError(w, http.StatusInternalServerError, "streaming is not supported")
return
}
actorUserID := r.URL.Query().Get("actor_user_id")
clusterID := chi.URLParam(r, "clusterID")
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache, no-transform")
w.Header().Set("Connection", "keep-alive")
w.Header().Set("X-Accel-Buffering", "no")
_, _ = fmt.Fprint(w, "retry: 5000\n\n")
flusher.Flush()
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
var lastRevision string
for {
revision, payload, err := m.clusterEventSnapshot(r, actorUserID, clusterID)
if err != nil {
_ = writeSSE(w, "cluster.error", map[string]any{
"cluster_id": clusterID,
"error": err.Error(),
"observed_at": time.Now().UTC(),
})
flusher.Flush()
return
}
if revision != lastRevision {
lastRevision = revision
_ = writeSSE(w, "cluster.changed", payload)
flusher.Flush()
} else {
_, _ = fmt.Fprintf(w, ": keepalive %s\n\n", time.Now().UTC().Format(time.RFC3339Nano))
flusher.Flush()
}
select {
case <-r.Context().Done():
return
case <-ticker.C:
}
}
}
func (m *Module) clusterEventSnapshot(r *http.Request, actorUserID, clusterID string) (string, map[string]any, error) {
ctx := r.Context()
summaries, err := m.service.ListClusterAdminSummaries(ctx, actorUserID)
if err != nil {
return "", nil, err
}
var selected *ClusterAdminSummary
for i := range summaries {
if summaries[i].ClusterID == clusterID {
selected = &summaries[i]
break
}
}
if selected == nil {
return "", nil, pgx.ErrNoRows
}
nodes, err := m.service.ListClusterNodes(ctx, actorUserID, clusterID)
if err != nil {
return "", nil, err
}
joinRequests, err := m.service.ListJoinRequests(ctx, actorUserID, clusterID)
if err != nil {
return "", nil, err
}
meshLinks, err := m.service.ListMeshLinks(ctx, actorUserID, clusterID)
if err != nil {
return "", nil, err
}
payload := map[string]any{
"cluster_id": clusterID,
"observed_at": time.Now().UTC(),
"node_count": len(nodes),
"join_request_count": len(joinRequests),
"mesh_link_count": len(meshLinks),
"summary": selected,
"latest_node_seen_at": latestNodeSeenAt(nodes),
"latest_mesh_seen_at": latestMeshLinkSeenAt(meshLinks),
}
revisionPayload := map[string]any{
"cluster_id": clusterID,
"node_count": len(nodes),
"join_request_count": len(joinRequests),
"mesh_link_count": len(meshLinks),
"summary": selected,
"latest_node_seen_at": latestNodeSeenAt(nodes),
"latest_mesh_seen_at": latestMeshLinkSeenAt(meshLinks),
}
revisionBytes, err := json.Marshal(revisionPayload)
if err != nil {
return "", nil, err
}
sum := sha256.Sum256(revisionBytes)
revision := hex.EncodeToString(sum[:])
payload["revision"] = revision
return revision, payload, nil
}
func writeSSE(w http.ResponseWriter, event string, payload any) error {
encoded, err := json.Marshal(payload)
if err != nil {
return err
}
if _, err := fmt.Fprintf(w, "event: %s\n", event); err != nil {
return err
}
if _, err := fmt.Fprintf(w, "data: %s\n\n", encoded); err != nil {
return err
}
return nil
}
func latestNodeSeenAt(nodes []ClusterNode) *time.Time {
var latest *time.Time
for i := range nodes {
if nodes[i].LastSeenAt == nil {
continue
}
if latest == nil || nodes[i].LastSeenAt.After(*latest) {
value := nodes[i].LastSeenAt.UTC()
latest = &value
}
}
return latest
}
func latestMeshLinkSeenAt(links []MeshLinkObservation) *time.Time {
var latest *time.Time
for i := range links {
if latest == nil || links[i].ObservedAt.After(*latest) {
value := links[i].ObservedAt.UTC()
latest = &value
}
}
return latest
}
func writeServiceError(w http.ResponseWriter, err error) bool {
if err == nil {
return false
}
switch {
case errors.Is(err, ErrAccessDenied):
httpx.WriteError(w, http.StatusForbidden, err.Error())
case errors.Is(err, ErrVPNLeaseOwnerNotAllowed), errors.Is(err, ErrVPNLeaseOwnerRoleRequired):
httpx.WriteError(w, http.StatusForbidden, err.Error())
case errors.Is(err, ErrClusterReadOnly):
httpx.WriteError(w, http.StatusConflict, err.Error())
case errors.Is(err, ErrVPNLeaseAlreadyActive):
httpx.WriteError(w, http.StatusConflict, err.Error())
case errors.Is(err, ErrInvalidPayload), errors.Is(err, ErrInvalidJoinToken), errors.Is(err, ErrInvalidNodeRole):
httpx.WriteError(w, http.StatusBadRequest, err.Error())
case errors.Is(err, ErrInvalidCluster), errors.Is(err, ErrInvalidJoinRequest), errors.Is(err, ErrInvalidVPNConnection), errors.Is(err, ErrInvalidVPNLease), errors.Is(err, pgx.ErrNoRows):
httpx.WriteError(w, http.StatusNotFound, err.Error())
default:
httpx.WriteError(w, http.StatusInternalServerError, err.Error())
}
return true
}