98 lines
2.9 KiB
Go
98 lines
2.9 KiB
Go
// Package sessions — FleetClient calls the SandboxFleet controller REST API
|
|||
|
|
// to assign sessions to fleet pods. This is the primary sandbox path;
|
||
|
|
// the raw-k8s SandboxManager is the fallback when the fleet controller
|
||
|
|
// is unavailable (e.g. during fleet rollout).
|
||
|
|
package sessions
|
||
|
|
|
||
|
|
import (
|
||
|
|
"bytes"
|
||
|
|
"context"
|
||
|
|
"encoding/json"
|
||
|
|
"fmt"
|
||
|
|
"io"
|
||
|
|
"net/http"
|
||
|
|
"os"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
const (
|
||
|
|
defaultFleetControllerURL = "http://tool-definition-controller.poimen.svc.cluster.local:8788"
|
||
|
|
)
|
||
|
|
|
||
|
|
// FleetClient calls the SandboxFleet controller REST API.
|
||
|
|
type FleetClient struct {
|
||
|
|
baseURL string
|
||
|
|
client *http.Client
|
||
|
|
}
|
||
|
|
|
||
|
|
// FleetAssignResult is returned by the controller assign endpoint.
|
||
|
|
type FleetAssignResult struct {
|
||
|
|
PodSvcDns string `json:"podSvcDns"`
|
||
|
|
PodName string `json:"podName"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewFleetClient creates a FleetClient from env or default URL.
|
||
|
|
// FLEET_CONTROLLER_URL — default: defaultFleetControllerURL
|
||
|
|
func NewFleetClient() *FleetClient {
|
||
|
|
url := os.Getenv("FLEET_CONTROLLER_URL")
|
||
|
|
if url == "" {
|
||
|
|
url = defaultFleetControllerURL
|
||
|
|
}
|
||
|
|
return &FleetClient{
|
||
|
|
baseURL: url,
|
||
|
|
client: &http.Client{Timeout: 35 * time.Second},
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Assign asks the fleet controller to assign sessionID to a fleet pod.
|
||
|
|
// Returns the pod's ClusterIP service DNS and pod name.
|
||
|
|
func (f *FleetClient) Assign(ctx context.Context, sessionID string) (*FleetAssignResult, error) {
|
||
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost,
|
||
|
|
f.baseURL+"/sessions/"+sessionID+"/assign", nil)
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("build request: %w", err)
|
||
|
|
}
|
||
|
|
req.Header.Set("Content-Type", "application/json")
|
||
|
|
|
||
|
|
resp, err := f.client.Do(req)
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("fleet controller unreachable: %w", err)
|
||
|
|
}
|
||
|
|
defer resp.Body.Close()
|
||
|
|
|
||
|
|
if resp.StatusCode >= 300 {
|
||
|
|
body, _ := io.ReadAll(io.LimitReader(resp.Body, 512))
|
||
|
|
return nil, fmt.Errorf("fleet controller returned %d: %s", resp.StatusCode, body)
|
||
|
|
}
|
||
|
|
|
||
|
|
var result FleetAssignResult
|
||
|
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||
|
|
return nil, fmt.Errorf("decode: %w", err)
|
||
|
|
}
|
||
|
|
if result.PodSvcDns == "" {
|
||
|
|
return nil, fmt.Errorf("fleet controller returned empty podSvcDns")
|
||
|
|
}
|
||
|
|
return &result, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// Release notifies the fleet controller that a session has ended.
|
||
|
|
// Cleans up the per-session ServiceAccount (audit trail SA).
|
||
|
|
// Non-fatal: fleet controller handles this async.
|
||
|
|
func (f *FleetClient) Release(ctx context.Context, sessionID string) error {
|
||
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodDelete,
|
||
|
|
f.baseURL+"/sessions/"+sessionID, bytes.NewReader(nil))
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("build request: %w", err)
|
||
|
|
}
|
||
|
|
resp, err := f.client.Do(req)
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("fleet controller unreachable: %w", err)
|
||
|
|
}
|
||
|
|
defer resp.Body.Close()
|
||
|
|
if resp.StatusCode >= 300 {
|
||
|
|
body, _ := io.ReadAll(io.LimitReader(resp.Body, 512))
|
||
|
|
return fmt.Errorf("fleet controller returned %d: %s", resp.StatusCode, body)
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|