Files
seaweedfs/weed/admin/dash/vector_status.go
Chris LuandClaude Opus 5.5 405efc2fbb admin: vector servers on the dashboard
A Vector servers count in the Cluster card and a Vector Servers table
after S3 Servers, from the engines the dashboard already discovers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK
2026-11-12 22:54:46 -08:00

110 lines
3.5 KiB
Go

package dash
import (
"context"
"sort"
"time"
adminplugin "github.com/seaweedfs/seaweedfs/weed/admin/plugin"
"github.com/seaweedfs/seaweedfs/weed/cluster"
"github.com/seaweedfs/seaweedfs/weed/pb"
"github.com/seaweedfs/seaweedfs/weed/pb/master_pb"
"golang.org/x/sync/errgroup"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/grpc/status"
)
type VectorNode struct {
Address string `json:"address"`
// Kind is "engine" for a weed-vector engine.
Kind string `json:"kind"`
Version string `json:"version"`
Status string `json:"status"`
}
type VectorOperationsData struct {
Engines []VectorNode `json:"engines"`
DiscoveryError string `json:"discovery_error,omitempty"`
MaintenanceWorkers int `json:"maintenance_workers"`
MaintenanceJobs []adminplugin.TrackedJob `json:"maintenance_jobs"`
}
func (s *AdminServer) getVectorNodesStatus(ctx context.Context) VectorOperationsData {
data := VectorOperationsData{Engines: []VectorNode{}}
if s.masterClient == nil {
data.DiscoveryError = "Master discovery unavailable"
return data
}
var discoveryErr error
for _, master := range s.masterClient.ListCachedMasters() {
discoveryCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
conn, err := pb.GrpcDial(discoveryCtx, master.ToGrpcAddress(), false, s.grpcDialOption)
if err == nil {
response, callErr := master_pb.NewSeaweedClient(conn).ListClusterNodes(discoveryCtx, s.listClusterNodesRequest(cluster.VectorType))
_ = conn.Close()
err = callErr
if err == nil {
for _, node := range response.ClusterNodes {
data.Engines = append(data.Engines, VectorNode{Address: node.Address, Kind: "engine", Version: node.Version, Status: "Registered"})
}
}
}
cancel()
if err != nil {
discoveryErr = err
}
if len(data.Engines) > 0 {
break
}
}
if len(data.Engines) == 0 && discoveryErr != nil {
data.DiscoveryError = discoveryErr.Error()
return data
}
var checks errgroup.Group
checks.SetLimit(8)
for i := range data.Engines {
checks.Go(func() error {
data.Engines[i].Status = vectorNodeStatus(ctx, data.Engines[i].Address, s.grpcDialOption)
return nil
})
}
_ = checks.Wait()
sort.Slice(data.Engines, func(i, j int) bool { return data.Engines[i].Address < data.Engines[j].Address })
return data
}
func vectorNodeStatus(ctx context.Context, address string, dialOption grpc.DialOption) string {
ctx, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()
conn, err := pb.GrpcDial(ctx, address, false, dialOption)
if err != nil {
return "Unreachable"
}
defer conn.Close()
response, err := grpc_health_v1.NewHealthClient(conn).Check(ctx, &grpc_health_v1.HealthCheckRequest{Service: "vector_pb.VectorService"})
if status.Code(err) == codes.Unimplemented || status.Code(err) == codes.NotFound {
return "Registered (health unavailable)"
}
if err != nil {
return "Unreachable"
}
if response.Status == grpc_health_v1.HealthCheckResponse_SERVING {
return "Ready"
}
return "Not ready"
}
func (s *AdminServer) GetVectorOperationsData(ctx context.Context) VectorOperationsData {
data := s.getVectorNodesStatus(ctx)
for _, worker := range s.GetPluginWorkers() {
if capability := worker.Capabilities["lance_vector_index"]; capability != nil && capability.CanExecute {
data.MaintenanceWorkers++
}
}
data.MaintenanceJobs = s.ListPluginJobs("lance_vector_index", "", 5)
return data
}