package api import ( "encoding/json" "fmt" "io" "log" "net/http" "strings" "time" "git.dws.rip/dubey/kat/internal/store" ) // NodeStatusRequest represents the data sent by an agent in a heartbeat type NodeStatusRequest struct { NodeName string `json:"nodeName"` NodeUID string `json:"nodeUID"` Timestamp time.Time `json:"timestamp"` Resources struct { Capacity map[string]string `json:"capacity"` Allocatable map[string]string `json:"allocatable"` } `json:"resources"` WorkloadInstances []struct { WorkloadName string `json:"workloadName"` Namespace string `json:"namespace"` InstanceID string `json:"instanceID"` ContainerID string `json:"containerID"` ImageID string `json:"imageID"` State string `json:"state"` ExitCode int `json:"exitCode"` HealthStatus string `json:"healthStatus"` Restarts int `json:"restarts"` } `json:"workloadInstances,omitempty"` OverlayNetwork struct { Status string `json:"status"` LastPeerSync string `json:"lastPeerSync"` } `json:"overlayNetwork"` } // NewNodeStatusHandler creates a handler for node status updates func NewNodeStatusHandler(stateStore store.StateStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // Extract node name from URL path pathParts := strings.Split(r.URL.Path, "/") if len(pathParts) < 4 { http.Error(w, "Invalid URL path", http.StatusBadRequest) return } nodeName := pathParts[len(pathParts)-2] // /v1alpha1/nodes/{nodeName}/status log.Printf("Received status update from node: %s", nodeName) // Read and parse the request body body, err := io.ReadAll(r.Body) if err != nil { log.Printf("Failed to read request body: %v", err) http.Error(w, "Failed to read request body", http.StatusBadRequest) return } defer r.Body.Close() var statusReq NodeStatusRequest if err := json.Unmarshal(body, &statusReq); err != nil { log.Printf("Failed to parse status request: %v", err) http.Error(w, "Failed to parse status request", http.StatusBadRequest) return } // Validate that the node name in the URL matches the one in the request if statusReq.NodeName != nodeName { log.Printf("Node name mismatch: %s (URL) vs %s (body)", nodeName, statusReq.NodeName) http.Error(w, "Node name mismatch", http.StatusBadRequest) return } // Store the node status in etcd nodeStatusKey := fmt.Sprintf("/kat/nodes/status/%s", nodeName) nodeStatus := map[string]interface{}{ "lastHeartbeat": time.Now().Unix(), "status": "Ready", "resources": statusReq.Resources, "network": statusReq.OverlayNetwork, } // Add workload instances if present if len(statusReq.WorkloadInstances) > 0 { nodeStatus["workloadInstances"] = statusReq.WorkloadInstances } nodeStatusData, err := json.Marshal(nodeStatus) if err != nil { log.Printf("Failed to marshal node status: %v", err) http.Error(w, "Failed to marshal node status", http.StatusInternalServerError) return } log.Printf("Storing node status in etcd at key: %s", nodeStatusKey) if err := stateStore.Put(r.Context(), nodeStatusKey, nodeStatusData); err != nil { log.Printf("Failed to store node status: %v", err) http.Error(w, "Failed to store node status", http.StatusInternalServerError) return } log.Printf("Successfully stored status update for node: %s", nodeName) w.WriteHeader(http.StatusOK) } }