mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-29 08:20:42 +01:00
vector: weed-vector registers to master; s3 discovers it there (#12146)
* vector: weed-vector registers with the weed master weed-vector-server --masters holds a KeepConnected stream to the weed master, the same mechanism filer and s3 use via wdclient — the server shows up in ListClusterNodes(client_type=vector). Resends registration and rediscovers on failure. * vector: s3 discovers weed-vector through the master With -vectors.grpc unset the vectors gateway resolves one registered weed-vector via ListClusterNodes on the masters it already derives from filer config — discovery replaces the static dial for the single-worker OSS path. The conformance harness registers the engine to mini's master and drops -vectors.grpc. * vector: default-fill registration fields ..Default::default() keeps the source compiling against proto variants that carry extra fields, so the same file works in either tree. * vector: serve the Vectors API on the main s3 port The vectors router sits on the main -port listener behind a dispatch that checks the SigV4 credential scope for /s3vectors/ — no separate port needed. -port.vectors stays for explicit dedicated-port deployments; the conformance harness now hits vectors through :8334. * vector: drop -port.vectors entirely It was new — no compat to preserve. The main -port listener serves the API whenever a weed-vector backend is reachable. * vector: drop -vectors.grpc; discovery is the only locator weed-vector always registers to master, the gateway always resolves it through ListClusterNodes — a static address just forks membership. * vector: binary is weed-vector Matches the enterprise binary; package and lib names are unchanged. * vector: review fixes — cluster type, grpc port, lazy resolve, leader hint, advertise - cluster: register 'vector' as a node type so ListClusterNodes returns weed-vector members - s3: dial masters via ToGrpcAddress (HTTP port + 10000) - s3: backend resolves lazily through vectorDiscovery — workers joining after gateway startup work on first query, and address changes re-dial - master_client: convert master HTTP addr to grpc +10000, honor http(s) scheme, follow the leader hint in KeepConnectedResponse, and --advertise covers wildcard/unreachable listen addresses * vector: no --advertise flag; derive the outbound address A wildcard listen resolves to the source IP the master sees via a route lookup, so registration always advertises something reachable without another flag. * vector: review fixes — plaintext dispatch, conn leaks, discovery backoff The two plaintext http servers now serve the vectors dispatch too — signed s3vectors calls on the main port reach the right router. discoverVectorWorker closes each master conn and bounds lookups to 5s; a dead or absent worker no longer re-hits master on every RPC — the resolver backs off for 5s between rediscovery attempts. * vector: 1 GiB grpc cap, dead-worker invalidation, CLI rename Valid PutVectors batches exceed the tonic 4 MiB default — match the gateway's 1 GiB cap. A replaced worker no longer sticks: any RPC error resets the cached address and the next call rediscovers through the master. The core CLI bin renames to weed-vector-cli so the two crates do not collide on the same output path. * vector: retry-only-transport reset, periodic re-resolve, cli rename - reset() takes the failed client and runs only on transport errors — an InvalidArgument on a healthy conn no longer interrupts unrelated requests, and a concurrent re-resolve wins the race. - cli() re-resolves once the interval passes even with a live client, so a worker that moves addresses is picked up. - verify.rs spike docs use weed-vector-cli after the bin rename. * vector: only Unavailable counts as a transport failure A request deadline or an application error can fire on a healthy connection — resetting the shared conn on those churned reconnects.
This commit is contained in:
1 parent
81879fba7b
commit
190ab32cfb
15 files changed
+423
-116
No files matched your search
+241
-97
@@ -5,12 +5,12 @@ import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/seaweedfs/seaweedfs/weed/glog"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/master_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/s3_lifecycle_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/s3_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/vector_pb"
|
||||
@@ -36,6 +37,8 @@ import (
|
||||
util_http "github.com/seaweedfs/seaweedfs/weed/util/http"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util/version"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -55,8 +58,6 @@ type S3Options struct {
|
||||
portGrpc *int
|
||||
portIceberg *int
|
||||
portLance *int
|
||||
portVectors *int
|
||||
vectorsGrpc *string
|
||||
icebergCredentialRole *string
|
||||
icebergCredentialDuration *int
|
||||
config *string
|
||||
@@ -115,8 +116,6 @@ func init() {
|
||||
s3StandaloneOptions.portGrpc = cmdS3.Flag.Int("port.grpc", 0, "s3 server grpc listen port")
|
||||
s3StandaloneOptions.portIceberg = cmdS3.Flag.Int("port.iceberg", 8181, "Iceberg REST Catalog server listen port (0 to disable)")
|
||||
s3StandaloneOptions.portLance = cmdS3.Flag.Int("port.lance", 9101, "Lance Namespace server listen port (0 to disable); credential vending uses -iceberg.credentialRole")
|
||||
s3StandaloneOptions.portVectors = cmdS3.Flag.Int("port.vectors", 0, "S3 Vectors API server listen port (0 to disable); requires -vectors.grpc")
|
||||
s3StandaloneOptions.vectorsGrpc = cmdS3.Flag.String("vectors.grpc", "", "weed-vector-server gRPC address for the S3 Vectors API")
|
||||
s3StandaloneOptions.icebergCredentialRole = cmdS3.Flag.String("iceberg.credentialRole", "", "IAM role ARN the Iceberg catalog assumes to vend table-scoped credentials (empty disables vending)")
|
||||
s3StandaloneOptions.icebergCredentialDuration = cmdS3.Flag.Int("iceberg.credentialDurationSeconds", 3600, "lifetime of credentials vended by the Iceberg catalog")
|
||||
s3StandaloneOptions.domainName = cmdS3.Flag.String("domainName", "", "suffix of the host name in comma separated list, {bucket}.{domainName}")
|
||||
@@ -440,11 +439,19 @@ func (s3opt *S3Options) startS3Server() bool {
|
||||
}
|
||||
}
|
||||
|
||||
// Start S3 Vectors API server if enabled
|
||||
if s3opt.portVectors != nil && *s3opt.portVectors > 0 {
|
||||
if httpS := s3opt.startVectorsServer(s3ApiServer, grpcDialOption, listenOnLocalhost); httpS != nil {
|
||||
httpServers = append(httpServers, httpS)
|
||||
}
|
||||
// The Vectors API rides the main -port listener whenever a weed-vector
|
||||
// backend is reachable — /s3vectors/ in the SigV4 credential scope picks
|
||||
// the vectors router.
|
||||
vectorsHandler := http.Handler(router)
|
||||
if vectorsRouter := s3opt.vectorsRouter(s3ApiServer, grpcDialOption, masterAddresses); vectorsRouter != nil {
|
||||
vr, mainRouter := vectorsRouter, router
|
||||
vectorsHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if isVectorsRequest(r) {
|
||||
vr.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
mainRouter.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
if runtime.GOOS != "windows" {
|
||||
@@ -455,7 +462,7 @@ func (s3opt *S3Options) startS3Server() bool {
|
||||
}
|
||||
if s3SocketListener := pb.ListenOnLocalSocket(localSocket); s3SocketListener != nil {
|
||||
s3SocketListener = &util.Listener{Listener: s3SocketListener, Timeout: idleTimeoutSeconds(s3opt.idleTimeout)}
|
||||
socketServer := newHttpServer(router, nil)
|
||||
socketServer := newHttpServer(vectorsHandler, nil)
|
||||
httpServers = append(httpServers, socketServer)
|
||||
go func() {
|
||||
if err := socketServer.Serve(s3SocketListener); err != nil && err != http.ErrServerClosed {
|
||||
@@ -530,7 +537,7 @@ func (s3opt *S3Options) startS3Server() bool {
|
||||
if *s3opt.portHttps == 0 {
|
||||
glog.V(0).Infof("Start Seaweed S3 API Server %s at https port %d", version.Version(), *s3opt.port)
|
||||
if s3ApiLocalListener != nil {
|
||||
localServer := newHttpServer(router, tlsConfig)
|
||||
localServer := newHttpServer(vectorsHandler, tlsConfig)
|
||||
httpServers = append(httpServers, localServer)
|
||||
go func() {
|
||||
if err := localServer.ServeTLS(s3ApiLocalListener, "", ""); err != nil && err != http.ErrServerClosed {
|
||||
@@ -538,7 +545,7 @@ func (s3opt *S3Options) startS3Server() bool {
|
||||
}
|
||||
}()
|
||||
}
|
||||
httpS := newHttpServer(router, tlsConfig)
|
||||
httpS := newHttpServer(vectorsHandler, tlsConfig)
|
||||
httpServers = append(httpServers, httpS)
|
||||
shutdown := s3opt.newShutdown(stopGrpcServer, s3ApiServer, httpServers)
|
||||
if err = httpS.ServeTLS(s3ApiListener, "", ""); err != nil && err != http.ErrServerClosed {
|
||||
@@ -554,7 +561,7 @@ func (s3opt *S3Options) startS3Server() bool {
|
||||
glog.Fatalf("S3 API HTTPS listener on %s:%d error: %v", *s3opt.bindIp, *s3opt.portHttps, err)
|
||||
}
|
||||
if s3ApiLocalListenerHttps != nil {
|
||||
localHttpsServer := newHttpServer(router, tlsConfig)
|
||||
localHttpsServer := newHttpServer(vectorsHandler, tlsConfig)
|
||||
httpServers = append(httpServers, localHttpsServer)
|
||||
go func() {
|
||||
if err := localHttpsServer.ServeTLS(s3ApiLocalListenerHttps, "", ""); err != nil && err != http.ErrServerClosed {
|
||||
@@ -562,7 +569,7 @@ func (s3opt *S3Options) startS3Server() bool {
|
||||
}
|
||||
}()
|
||||
}
|
||||
httpsServer := newHttpServer(router, tlsConfig)
|
||||
httpsServer := newHttpServer(vectorsHandler, tlsConfig)
|
||||
httpServers = append(httpServers, httpsServer)
|
||||
go func() {
|
||||
if err := httpsServer.ServeTLS(s3ApiListenerHttps, "", ""); err != nil && err != http.ErrServerClosed {
|
||||
@@ -574,7 +581,7 @@ func (s3opt *S3Options) startS3Server() bool {
|
||||
if *s3opt.tlsPrivateKey == "" || *s3opt.portHttps > 0 {
|
||||
glog.V(0).Infof("Start Seaweed S3 API Server %s at http port %d", version.Version(), *s3opt.port)
|
||||
if s3ApiLocalListener != nil {
|
||||
localServer := newHttpServer(router, nil)
|
||||
localServer := newHttpServer(vectorsHandler, nil)
|
||||
httpServers = append(httpServers, localServer)
|
||||
go func() {
|
||||
if err := localServer.Serve(s3ApiLocalListener); err != nil && err != http.ErrServerClosed {
|
||||
@@ -582,7 +589,7 @@ func (s3opt *S3Options) startS3Server() bool {
|
||||
}
|
||||
}()
|
||||
}
|
||||
httpS := newHttpServer(router, nil)
|
||||
httpS := newHttpServer(vectorsHandler, nil)
|
||||
httpServers = append(httpServers, httpS)
|
||||
shutdown := s3opt.newShutdown(stopGrpcServer, s3ApiServer, httpServers)
|
||||
if err = httpS.Serve(s3ApiListener); err != nil && err != http.ErrServerClosed {
|
||||
@@ -702,107 +709,221 @@ func (s3opt *S3Options) startLanceServer(s3ApiServer *s3api.S3ApiServer, listenO
|
||||
return httpS
|
||||
}
|
||||
|
||||
// startVectorsServer starts the S3 Vectors API server on a separate port. It
|
||||
// fans index and data calls out to the shared VectorService over gRPC; with no
|
||||
// -vectors.grpc the API is not served.
|
||||
func (s3opt *S3Options) startVectorsServer(s3ApiServer *s3api.S3ApiServer, grpcDialOption grpc.DialOption, listenOnLocalhost bool) *http.Server {
|
||||
if *s3opt.vectorsGrpc == "" {
|
||||
glog.Warningf("port.vectors set without -vectors.grpc; S3 Vectors API disabled")
|
||||
return nil
|
||||
}
|
||||
conn, err := pb.GrpcDial(context.Background(), *s3opt.vectorsGrpc, false, grpcDialOption)
|
||||
if err != nil {
|
||||
glog.Errorf("S3 Vectors: dial %s: %v", *s3opt.vectorsGrpc, err)
|
||||
// vectorsRouter builds the S3 Vectors API router — workers register to the
|
||||
// master and the backend resolves them lazily, so a worker that comes up
|
||||
// after the gateway still gets picked up on first query.
|
||||
func (s3opt *S3Options) vectorsRouter(s3ApiServer *s3api.S3ApiServer, grpcDialOption grpc.DialOption, masterAddresses []pb.ServerAddress) *mux.Router {
|
||||
if len(masterAddresses) == 0 {
|
||||
glog.Warningf("S3 Vectors API disabled: no weed-vector registered on master")
|
||||
return nil
|
||||
}
|
||||
backend := &vectorDiscovery{masters: masterAddresses, grpcDialOption: grpcDialOption}
|
||||
vectorsRouter := mux.NewRouter().SkipClean(true)
|
||||
|
||||
vectorsServer := s3vectors.NewServer(s3ApiServer, s3ApiServer, vector_pb.NewVectorServiceClient(conn))
|
||||
vectorsServer := s3vectors.NewServer(s3ApiServer, s3ApiServer, backend)
|
||||
vectorsServer.SetRegion(s3tables.DefaultRegion)
|
||||
vectorsServer.SetAccountID(s3tables.DefaultAccountID)
|
||||
vectorsServer.RegisterRoutes(vectorsRouter)
|
||||
return vectorsRouter
|
||||
}
|
||||
|
||||
// Mirror the main API: when TLS is configured the vectors port serves
|
||||
// HTTPS, not a second plaintext endpoint. Build the TLS config before
|
||||
// binding listeners so a certificate failure does not leave ports bound.
|
||||
var tlsConfig *tls.Config
|
||||
if *s3opt.tlsPrivateKey != "" {
|
||||
// The reloading provider reads the key pair asynchronously, so validate
|
||||
// it once up front: a missing or mismatched pair must fail here rather
|
||||
// than after the listeners bind an endpoint that cannot handshake.
|
||||
if _, err := tls.LoadX509KeyPair(*s3opt.tlsCertificate, *s3opt.tlsPrivateKey); err != nil {
|
||||
glog.Errorf("S3 Vectors: load HTTPS certificate: %v", err)
|
||||
conn.Close()
|
||||
return nil
|
||||
}
|
||||
getCert, _, err := security.NewReloadingServerCertificate(*s3opt.tlsCertificate, *s3opt.tlsPrivateKey)
|
||||
// vectorDiscovery resolves the currently registered weed-vector through the
|
||||
// master and caches the connection; workers joining after gateway startup
|
||||
// are picked up on first query.
|
||||
type vectorDiscovery struct {
|
||||
masters []pb.ServerAddress
|
||||
grpcDialOption grpc.DialOption
|
||||
mu sync.Mutex
|
||||
worker string
|
||||
conn *grpc.ClientConn
|
||||
client vector_pb.VectorServiceClient
|
||||
nextResolve time.Time
|
||||
}
|
||||
|
||||
// reset drops the cached worker so the next call rediscovers — invoked on
|
||||
// transport errors only, and only when the failed client is still the
|
||||
// cached one (a concurrent request may have already re-resolved).
|
||||
func (d *vectorDiscovery) reset(c vector_pb.VectorServiceClient) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
if c != d.client {
|
||||
return
|
||||
}
|
||||
if d.conn != nil {
|
||||
d.conn.Close()
|
||||
}
|
||||
d.worker, d.conn, d.client = "", nil, nil
|
||||
d.nextResolve = time.Time{}
|
||||
}
|
||||
|
||||
// Only Unavailable proves a transport failure — a request deadline or an
|
||||
// application-level error can fire on a healthy connection.
|
||||
func isTransportError(err error) bool {
|
||||
return status.Code(err) == codes.Unavailable
|
||||
}
|
||||
|
||||
func (d *vectorDiscovery) resolve() (vector_pb.VectorServiceClient, error) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
worker := discoverVectorWorker(d.masters, d.grpcDialOption)
|
||||
if worker == "" {
|
||||
return nil, fmt.Errorf("no weed-vector registered on master")
|
||||
}
|
||||
if worker != d.worker {
|
||||
if d.conn != nil {
|
||||
d.conn.Close()
|
||||
}
|
||||
conn, err := pb.GrpcDial(context.Background(), worker, false, d.grpcDialOption)
|
||||
if err != nil {
|
||||
glog.Errorf("S3 Vectors: load HTTPS certificate: %v", err)
|
||||
conn.Close()
|
||||
return nil
|
||||
}
|
||||
clientAuth := tls.NoClientCert
|
||||
var caCertPool *x509.CertPool
|
||||
if *s3opt.tlsVerifyClientCert {
|
||||
clientAuth = tls.RequireAndVerifyClientCert
|
||||
caCertPool, err = loadClientCAPool(*s3opt.tlsCACertificate)
|
||||
if err != nil {
|
||||
glog.Errorf("S3 Vectors: %v", err)
|
||||
conn.Close()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
tlsConfig = &tls.Config{
|
||||
GetCertificate: getCert,
|
||||
ClientAuth: clientAuth,
|
||||
ClientCAs: caCertPool,
|
||||
}
|
||||
if err := security.FixTlsConfig(util.GetViper(), tlsConfig); err != nil {
|
||||
glog.Errorf("S3 Vectors: tls config: %v", err)
|
||||
conn.Close()
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
d.worker, d.conn = worker, conn
|
||||
d.client = vector_pb.NewVectorServiceClient(conn)
|
||||
}
|
||||
return d.client, nil
|
||||
}
|
||||
|
||||
listenAddress := fmt.Sprintf("%s:%d", *s3opt.bindIp, *s3opt.portVectors)
|
||||
vectorsListener, vectorsLocalListener, err := util.NewIpAndLocalListeners(
|
||||
*s3opt.bindIp, *s3opt.portVectors, idleTimeoutSeconds(s3opt.idleTimeout), listenOnLocalhost)
|
||||
if err != nil {
|
||||
glog.Errorf("S3 Vectors listener on %s error: %v", listenAddress, err)
|
||||
conn.Close()
|
||||
return nil
|
||||
const vectorResolveInterval = 5 * time.Second
|
||||
|
||||
func (d *vectorDiscovery) cli() (vector_pb.VectorServiceClient, error) {
|
||||
d.mu.Lock()
|
||||
cli := d.client
|
||||
past := !time.Now().Before(d.nextResolve)
|
||||
if cli != nil && !past {
|
||||
d.mu.Unlock()
|
||||
return cli, nil
|
||||
}
|
||||
if !past && cli == nil {
|
||||
d.mu.Unlock()
|
||||
return nil, fmt.Errorf("no weed-vector registered on master")
|
||||
}
|
||||
d.nextResolve = time.Now().Add(vectorResolveInterval)
|
||||
d.mu.Unlock()
|
||||
return d.resolve()
|
||||
}
|
||||
|
||||
if tlsConfig != nil {
|
||||
glog.V(0).Infof("Start S3 Vectors API Server at https://%s", listenAddress)
|
||||
} else {
|
||||
glog.V(0).Infof("Start S3 Vectors API Server at http://%s", listenAddress)
|
||||
func (d *vectorDiscovery) CreateIndex(ctx context.Context, in *vector_pb.CreateIndexRequest, opts ...grpc.CallOption) (resp *vector_pb.CreateIndexResponse, err error) {
|
||||
for attempt := 0; attempt < 2; attempt++ {
|
||||
var c vector_pb.VectorServiceClient
|
||||
if c, err = d.cli(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp, err = c.CreateIndex(ctx, in, opts...); err == nil {
|
||||
return resp, nil
|
||||
}
|
||||
if !isTransportError(err) {
|
||||
return nil, err
|
||||
}
|
||||
d.reset(c)
|
||||
}
|
||||
|
||||
httpS := newHttpServer(vectorsRouter, tlsConfig)
|
||||
serve := func(listener net.Listener) error {
|
||||
if tlsConfig != nil {
|
||||
return httpS.ServeTLS(listener, "", "")
|
||||
return nil, err
|
||||
}
|
||||
func (d *vectorDiscovery) DropIndex(ctx context.Context, in *vector_pb.DropIndexRequest, opts ...grpc.CallOption) (resp *vector_pb.DropIndexResponse, err error) {
|
||||
for attempt := 0; attempt < 2; attempt++ {
|
||||
var c vector_pb.VectorServiceClient
|
||||
if c, err = d.cli(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp, err = c.DropIndex(ctx, in, opts...); err == nil {
|
||||
return resp, nil
|
||||
}
|
||||
if !isTransportError(err) {
|
||||
return nil, err
|
||||
}
|
||||
d.reset(c)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
func (d *vectorDiscovery) PutVectors(ctx context.Context, in *vector_pb.PutVectorsRequest, opts ...grpc.CallOption) (resp *vector_pb.PutVectorsResponse, err error) {
|
||||
for attempt := 0; attempt < 2; attempt++ {
|
||||
var c vector_pb.VectorServiceClient
|
||||
if c, err = d.cli(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp, err = c.PutVectors(ctx, in, opts...); err == nil {
|
||||
return resp, nil
|
||||
}
|
||||
if !isTransportError(err) {
|
||||
return nil, err
|
||||
}
|
||||
return httpS.Serve(listener)
|
||||
d.reset(c)
|
||||
}
|
||||
if vectorsLocalListener != nil {
|
||||
go func() {
|
||||
if err := serve(vectorsLocalListener); err != nil && err != http.ErrServerClosed {
|
||||
glog.V(0).Infof("S3 Vectors localhost listener error: %v", err)
|
||||
}
|
||||
}()
|
||||
return nil, err
|
||||
}
|
||||
func (d *vectorDiscovery) GetVectors(ctx context.Context, in *vector_pb.GetVectorsRequest, opts ...grpc.CallOption) (resp *vector_pb.GetVectorsResponse, err error) {
|
||||
for attempt := 0; attempt < 2; attempt++ {
|
||||
var c vector_pb.VectorServiceClient
|
||||
if c, err = d.cli(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp, err = c.GetVectors(ctx, in, opts...); err == nil {
|
||||
return resp, nil
|
||||
}
|
||||
if !isTransportError(err) {
|
||||
return nil, err
|
||||
}
|
||||
d.reset(c)
|
||||
}
|
||||
go func() {
|
||||
if err := serve(vectorsListener); err != nil && err != http.ErrServerClosed {
|
||||
glog.Errorf("S3 Vectors API Server Fail to serve: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
func (d *vectorDiscovery) DeleteVectors(ctx context.Context, in *vector_pb.DeleteVectorsRequest, opts ...grpc.CallOption) (resp *vector_pb.DeleteVectorsResponse, err error) {
|
||||
for attempt := 0; attempt < 2; attempt++ {
|
||||
var c vector_pb.VectorServiceClient
|
||||
if c, err = d.cli(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp, err = c.DeleteVectors(ctx, in, opts...); err == nil {
|
||||
return resp, nil
|
||||
}
|
||||
if !isTransportError(err) {
|
||||
return nil, err
|
||||
}
|
||||
d.reset(c)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
func (d *vectorDiscovery) ListVectors(ctx context.Context, in *vector_pb.ListVectorsRequest, opts ...grpc.CallOption) (resp *vector_pb.ListVectorsResponse, err error) {
|
||||
for attempt := 0; attempt < 2; attempt++ {
|
||||
var c vector_pb.VectorServiceClient
|
||||
if c, err = d.cli(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp, err = c.ListVectors(ctx, in, opts...); err == nil {
|
||||
return resp, nil
|
||||
}
|
||||
}()
|
||||
return httpS
|
||||
if !isTransportError(err) {
|
||||
return nil, err
|
||||
}
|
||||
d.reset(c)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
func (d *vectorDiscovery) QueryVectors(ctx context.Context, in *vector_pb.QueryVectorsRequest, opts ...grpc.CallOption) (resp *vector_pb.QueryVectorsResponse, err error) {
|
||||
for attempt := 0; attempt < 2; attempt++ {
|
||||
var c vector_pb.VectorServiceClient
|
||||
if c, err = d.cli(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp, err = c.QueryVectors(ctx, in, opts...); err == nil {
|
||||
return resp, nil
|
||||
}
|
||||
if !isTransportError(err) {
|
||||
return nil, err
|
||||
}
|
||||
d.reset(c)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// isVectorsRequest routes by the SigV4 credential scope — the s3vectors
|
||||
// service name lives there, not in the path.
|
||||
func isVectorsRequest(r *http.Request) bool {
|
||||
if v := r.Header.Get("Authorization"); v != "" {
|
||||
return strings.Contains(v, "/s3vectors/")
|
||||
}
|
||||
return strings.Contains(r.URL.Query().Get("X-Amz-Credential"), "/s3vectors/")
|
||||
}
|
||||
|
||||
// loadClientCAPool builds the client CA pool from -cacert.file, failing when
|
||||
// the file reads but carries no usable certificate.
|
||||
func loadClientCAPool(caCertFile string) (*x509.CertPool, error) {
|
||||
pool := x509.NewCertPool()
|
||||
if caCertFile == "" {
|
||||
@@ -928,3 +1049,26 @@ func (v lanceCredentialVendor) VendTableCredentials(ctx context.Context, princip
|
||||
Expiration: credentials.Expiration,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// discoverVectorWorker resolves one registered weed-vector server through the
|
||||
// master's cluster-nodes list; OSS fans every vector call to that single
|
||||
// backend.
|
||||
func discoverVectorWorker(masters []pb.ServerAddress, grpcDialOption grpc.DialOption) string {
|
||||
for _, master := range masters {
|
||||
conn, err := pb.GrpcDial(context.Background(), master.ToGrpcAddress(), false, grpcDialOption)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
resp, err := master_pb.NewSeaweedClient(conn).ListClusterNodes(context.Background(), &master_pb.ListClusterNodesRequest{
|
||||
ClientType: "vector",
|
||||
})
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, node := range resp.ClusterNodes {
|
||||
glog.V(0).Infof("S3 Vectors: discovered weed-vector at %s via master %s", node.Address, master)
|
||||
return node.Address
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
Reference in new issue
Block a user