Files
seaweedfs/sw-block/runtime/volumev2/operator_surface.go
T
pingqiuandClaude Opus 4.6 044a6d770b feat: Phase 19 — bounded working RF2 block path
Live HTTP evidence transport, continuous Loop2 service, bounded auto
failover trigger, runtime-managed frontend export, bounded replica
repair, end-to-end RF2 handoff with continued I/O on new primary,
bounded operator HTTP surface, and CSI V2 runtime backend adapter.

11 new proof tests covering the full M6-M10 chain plus CSI create/
lookup/publish through the V2 runtime path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 15:12:00 -07:00

124 lines
2.7 KiB
Go

package volumev2
import (
"context"
"encoding/json"
"fmt"
"net"
"net/http"
"strings"
)
// OperatorSurfaceHandle controls one bounded HTTP operator surface.
type OperatorSurfaceHandle struct {
addr string
server *http.Server
listener net.Listener
}
// Address returns the listen address of the operator surface.
func (h *OperatorSurfaceHandle) Address() string {
if h == nil {
return ""
}
return h.addr
}
// Close stops the operator surface.
func (h *OperatorSurfaceHandle) Close() error {
if h == nil {
return nil
}
if h.server != nil {
_ = h.server.Shutdown(context.Background())
}
if h.listener != nil {
return h.listener.Close()
}
return nil
}
// StartOperatorSurface starts one bounded HTTP operator surface over the
// runtime-owned snapshots and projections.
func (m *InProcessRuntimeManager) StartOperatorSurface(listenAddr string) (*OperatorSurfaceHandle, error) {
if m == nil {
return nil, fmt.Errorf("volumev2: runtime manager is nil")
}
if listenAddr == "" {
listenAddr = "127.0.0.1:0"
}
mux := http.NewServeMux()
mux.HandleFunc("/v1/volumes/", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
path := strings.TrimPrefix(r.URL.Path, "/v1/volumes/")
parts := strings.Split(strings.Trim(path, "/"), "/")
if len(parts) != 2 {
http.NotFound(w, r)
return
}
volumeName, viewName := parts[0], parts[1]
switch viewName {
case "rf2":
value, ok := m.RF2VolumeSurface(volumeName)
if !ok {
http.NotFound(w, r)
return
}
writeOperatorJSON(w, value)
case "failover":
value, ok := m.FailoverSnapshot(volumeName)
if !ok {
http.NotFound(w, r)
return
}
writeOperatorJSON(w, value)
case "loop2":
value, ok := m.Loop2Snapshot(volumeName)
if !ok {
http.NotFound(w, r)
return
}
writeOperatorJSON(w, value)
case "continuity":
value, ok := m.ReplicatedContinuitySnapshot(volumeName)
if !ok {
http.NotFound(w, r)
return
}
writeOperatorJSON(w, value)
case "frontend":
value, ok := m.ISCSIExport(volumeName)
if !ok {
http.NotFound(w, r)
return
}
writeOperatorJSON(w, value)
default:
http.NotFound(w, r)
}
})
ln, err := net.Listen("tcp", listenAddr)
if err != nil {
return nil, fmt.Errorf("volumev2: operator surface listen: %w", err)
}
server := &http.Server{Handler: mux}
handle := &OperatorSurfaceHandle{
addr: ln.Addr().String(),
server: server,
listener: ln,
}
go func() {
_ = server.Serve(ln)
}()
return handle, nil
}
func writeOperatorJSON(w http.ResponseWriter, value any) {
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(value)
}