You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
189 lines
5.9 KiB
189 lines
5.9 KiB
package wdclient
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"google.golang.org/grpc"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/glog"
|
|
"github.com/seaweedfs/seaweedfs/weed/pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
|
)
|
|
|
|
// UrlPreference controls which URL to use for volume access
|
|
type UrlPreference string
|
|
|
|
const (
|
|
PreferUrl UrlPreference = "url" // Use private URL (default)
|
|
PreferPublicUrl UrlPreference = "publicUrl" // Use public URL
|
|
)
|
|
|
|
// FilerClient provides volume location services by querying a filer
|
|
// It uses the shared vidMap cache for efficient lookups
|
|
type FilerClient struct {
|
|
*vidMapClient
|
|
filerAddress pb.ServerAddress
|
|
grpcDialOption grpc.DialOption
|
|
urlPreference UrlPreference
|
|
grpcTimeout time.Duration
|
|
cacheSize int // Number of historical vidMap snapshots to keep
|
|
}
|
|
|
|
// filerVolumeProvider implements VolumeLocationProvider by querying filer
|
|
type filerVolumeProvider struct {
|
|
filerAddress pb.ServerAddress
|
|
grpcDialOption grpc.DialOption
|
|
grpcTimeout time.Duration
|
|
}
|
|
|
|
// FilerClientOption holds optional configuration for FilerClient
|
|
type FilerClientOption struct {
|
|
GrpcTimeout time.Duration
|
|
UrlPreference UrlPreference
|
|
CacheSize int // Number of historical vidMap snapshots (0 = use default)
|
|
}
|
|
|
|
// NewFilerClient creates a new client that queries filer for volume locations
|
|
// Uses sensible defaults: 5-second gRPC timeout, PreferUrl, DefaultVidMapCacheSize
|
|
func NewFilerClient(filerAddress pb.ServerAddress, grpcDialOption grpc.DialOption, dataCenter string, opts ...*FilerClientOption) *FilerClient {
|
|
// Apply defaults
|
|
grpcTimeout := 5 * time.Second
|
|
urlPref := PreferUrl
|
|
cacheSize := DefaultVidMapCacheSize
|
|
|
|
// Override with provided options
|
|
if len(opts) > 0 && opts[0] != nil {
|
|
opt := opts[0]
|
|
if opt.GrpcTimeout > 0 {
|
|
grpcTimeout = opt.GrpcTimeout
|
|
}
|
|
if opt.UrlPreference != "" {
|
|
urlPref = opt.UrlPreference
|
|
}
|
|
if opt.CacheSize > 0 {
|
|
cacheSize = opt.CacheSize
|
|
}
|
|
}
|
|
|
|
provider := &filerVolumeProvider{
|
|
filerAddress: filerAddress,
|
|
grpcDialOption: grpcDialOption,
|
|
grpcTimeout: grpcTimeout,
|
|
}
|
|
|
|
return &FilerClient{
|
|
vidMapClient: newVidMapClient(provider, dataCenter, cacheSize),
|
|
filerAddress: filerAddress,
|
|
grpcDialOption: grpcDialOption,
|
|
urlPreference: urlPref,
|
|
grpcTimeout: grpcTimeout,
|
|
cacheSize: cacheSize,
|
|
}
|
|
}
|
|
|
|
// GetLookupFileIdFunction returns a lookup function with URL preference handling
|
|
func (fc *FilerClient) GetLookupFileIdFunction() LookupFileIdFunctionType {
|
|
if fc.urlPreference == PreferUrl {
|
|
// Use the default implementation from vidMapClient
|
|
return fc.vidMapClient.GetLookupFileIdFunction()
|
|
}
|
|
|
|
// Custom implementation that prefers PublicUrl
|
|
return func(ctx context.Context, fileId string) (fullUrls []string, err error) {
|
|
// Parse file ID to extract volume ID
|
|
parts := strings.Split(fileId, ",")
|
|
if len(parts) != 2 {
|
|
return nil, fmt.Errorf("invalid fileId format: %s", fileId)
|
|
}
|
|
volumeIdStr := parts[0]
|
|
|
|
// First try the cache using LookupVolumeIdsWithFallback
|
|
vidLocations, err := fc.LookupVolumeIdsWithFallback(ctx, []string{volumeIdStr})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("LookupVolume %s failed: %v", fileId, err)
|
|
}
|
|
|
|
locations, found := vidLocations[volumeIdStr]
|
|
if !found || len(locations) == 0 {
|
|
return nil, fmt.Errorf("volume %s not found for fileId %s", volumeIdStr, fileId)
|
|
}
|
|
|
|
// Build URLs with publicUrl preference
|
|
for _, loc := range locations {
|
|
url := loc.PublicUrl
|
|
if url == "" {
|
|
url = loc.Url
|
|
}
|
|
fullUrls = append(fullUrls, "http://"+url+"/"+fileId)
|
|
}
|
|
return fullUrls, nil
|
|
}
|
|
}
|
|
|
|
// LookupVolumeIds queries the filer for volume locations
|
|
// Note: Unlike master's VolumeIdLocation, filer's Locations message doesn't currently have
|
|
// an Error field. This implementation handles the current structure while being prepared
|
|
// for future error reporting enhancements.
|
|
func (p *filerVolumeProvider) LookupVolumeIds(ctx context.Context, volumeIds []string) (map[string][]Location, error) {
|
|
result := make(map[string][]Location)
|
|
|
|
// Create a timeout context for the gRPC call
|
|
timeoutCtx, cancel := context.WithTimeout(ctx, p.grpcTimeout)
|
|
defer cancel()
|
|
|
|
// Convert grpcTimeout to milliseconds for the signature parameter
|
|
timeoutMs := int32(p.grpcTimeout.Milliseconds())
|
|
|
|
err := pb.WithGrpcFilerClient(false, timeoutMs, p.filerAddress, p.grpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
|
|
resp, err := client.LookupVolume(timeoutCtx, &filer_pb.LookupVolumeRequest{
|
|
VolumeIds: volumeIds,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("filer.LookupVolume failed: %w", err)
|
|
}
|
|
|
|
// Process each volume in the response
|
|
for vid, locs := range resp.LocationsMap {
|
|
// Convert locations from protobuf to internal format
|
|
var locations []Location
|
|
for _, loc := range locs.Locations {
|
|
locations = append(locations, Location{
|
|
Url: loc.Url,
|
|
PublicUrl: loc.PublicUrl,
|
|
DataCenter: loc.DataCenter,
|
|
GrpcPort: int(loc.GrpcPort),
|
|
})
|
|
}
|
|
|
|
// Only add to result if we have locations
|
|
// Empty locations with no gRPC error means "not found" (volume doesn't exist)
|
|
if len(locations) > 0 {
|
|
result[vid] = locations
|
|
glog.V(4).Infof("FilerClient: volume %s found with %d location(s)", vid, len(locations))
|
|
} else {
|
|
glog.V(2).Infof("FilerClient: volume %s not found (no locations in response)", vid)
|
|
}
|
|
}
|
|
|
|
// Check for volumes that weren't in the response at all
|
|
// This could indicate a problem with the filer
|
|
for _, vid := range volumeIds {
|
|
if _, found := resp.LocationsMap[vid]; !found {
|
|
glog.V(1).Infof("FilerClient: volume %s missing from filer response", vid)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
// gRPC error - this is a communication or server failure
|
|
return nil, fmt.Errorf("filer volume lookup failed for %d volume(s): %w", len(volumeIds), err)
|
|
}
|
|
|
|
glog.V(3).Infof("FilerClient: looked up %d volumes, found %d", len(volumeIds), len(result))
|
|
return result, nil
|
|
}
|