mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-08 15:41:15 +02:00
* storage: make DeleteVolume errors inspectable with errors.Is An absent volume wraps ErrVolumeNotFound and an only-empty refusal now wraps ErrVolumeNotEmpty with %w instead of %v, so callers no longer have to match on the message. Claude-Session: https://claude.ai/code/session_01T4MEV3ETqFFKN46Uu2ZrUm * volume server: return NotFound and FailedPrecondition from VolumeDelete An absent volume maps to codes.NotFound and a non-empty volume under only_empty to codes.FailedPrecondition, so a caller retiring a volume can treat NotFound as already done. The store message is kept in the status description because the EC empty-replica sweep still matches on it. Claude-Session: https://claude.ai/code/session_01T4MEV3ETqFFKN46Uu2ZrUm * wdclient: add LookupVolumeIdsAuthoritative Bypasses the vid map and asks the provider directly, for callers where a stale positive location is unsafe. Claude-Session: https://claude.ai/code/session_01T4MEV3ETqFFKN46Uu2ZrUm * filer: add LookupDirectoryEntries batch lookup RPC Up to 4096 exact-path lookups in one call, resolved concurrently with results in request order, plus one deduplicated location lookup for every volume the returned entries reference and per-fid read tokens when the filer signs reads. unavailable_volume_is_miss lets cache-style callers take an entry whose volume has no live location as a miss, resolved against the master rather than the filer's location cache. Claude-Session: https://claude.ai/code/session_01T4MEV3ETqFFKN46Uu2ZrUm * filer: test that an expired file entry is deleted on read Claude-Session: https://claude.ai/code/session_01T4MEV3ETqFFKN46Uu2ZrUm * filer: test that AssignVolume and CreateEntry resolve the same TTL rule Claude-Session: https://claude.ai/code/session_01T4MEV3ETqFFKN46Uu2ZrUm * master: refuse partial lookups while warming up LookupVolume returned Unavailable during warm-up only when every requested volume was missing. A batch mixing a reported volume with one whose server has not reconnected yet came back as a partial answer with a per-volume not-found, which a caller treating the master as authoritative reads as gone. Any not-found during warm-up is now Unavailable, which callers already retry. Claude-Session: https://claude.ai/code/session_01T4MEV3ETqFFKN46Uu2ZrUm * filer: build batch test requests instead of copying a proto message Copying a generated message copies its internal mutex, which go vet's copylocks check rejects. Claude-Session: https://claude.ai/code/session_01T4MEV3ETqFFKN46Uu2ZrUm * filer: match ErrNotFound with errors.Is and state the miss rule's contract A wrapped not-found from the store would otherwise be reported as an error rather than a miss. The comments now say why a nil location map is the only sign of an unanswered lookup: the provider returns nil when it got no answer and a populated map, with unserved volumes reported as errors, when the master did answer. Claude-Session: https://claude.ai/code/session_01T4MEV3ETqFFKN46Uu2ZrUm * volume server: map absent and non-empty VolumeDelete errors in the Rust server Matches the Go server: an absent volume is NotFound and an only_empty refusal is FailedPrecondition instead of Internal, with the messages the EC empty-replica sweep matches on. Claude-Session: https://claude.ai/code/session_01T4MEV3ETqFFKN46Uu2ZrUm * filer: test that a malformed entry keeps its error outside cache mode Same test file as the enterprise tree, so the next sync sees one version. Claude-Session: https://claude.ai/code/session_01T4MEV3ETqFFKN46Uu2ZrUm
419 lines
16 KiB
Go
419 lines
16 KiB
Go
package weed_server
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/filer"
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/util"
|
|
"github.com/seaweedfs/seaweedfs/weed/wdclient"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
)
|
|
|
|
func TestLookupDirectoryEntriesRejectsInvalidRequests(t *testing.T) {
|
|
valid := &filer_pb.LookupDirectoryEntryRequest{Directory: "/batch", Name: "key"}
|
|
tests := []struct {
|
|
name string
|
|
req *filer_pb.LookupDirectoryEntriesRequest
|
|
wantCode codes.Code
|
|
}{
|
|
{name: "nil request", wantCode: codes.InvalidArgument},
|
|
{name: "empty batch", req: &filer_pb.LookupDirectoryEntriesRequest{}, wantCode: codes.InvalidArgument},
|
|
{name: "oversized", req: repeatedBatchLookupRequest(maxFilerBatchLookupRequests+1, valid), wantCode: codes.ResourceExhausted},
|
|
{name: "nil item", req: &filer_pb.LookupDirectoryEntriesRequest{Requests: []*filer_pb.LookupDirectoryEntryRequest{nil}}, wantCode: codes.InvalidArgument},
|
|
{name: "relative directory", req: &filer_pb.LookupDirectoryEntriesRequest{Requests: []*filer_pb.LookupDirectoryEntryRequest{{Directory: "batch", Name: "key"}}}, wantCode: codes.InvalidArgument},
|
|
{name: "empty name", req: &filer_pb.LookupDirectoryEntriesRequest{Requests: []*filer_pb.LookupDirectoryEntryRequest{{Directory: "/batch"}}}, wantCode: codes.InvalidArgument},
|
|
{name: "nested name", req: &filer_pb.LookupDirectoryEntriesRequest{Requests: []*filer_pb.LookupDirectoryEntryRequest{{Directory: "/batch", Name: "a/b"}}}, wantCode: codes.InvalidArgument},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
called := false
|
|
_, err := lookupDirectoryEntries(context.Background(), tt.req, 17,
|
|
func(context.Context, util.FullPath) (*filer.Entry, int64, error) {
|
|
called = true
|
|
return nil, 0, nil
|
|
},
|
|
func(context.Context, []string) (map[string][]wdclient.Location, error) {
|
|
called = true
|
|
return nil, nil
|
|
},
|
|
)
|
|
if status.Code(err) != tt.wantCode {
|
|
t.Fatalf("error code = %v, want %v: %v", status.Code(err), tt.wantCode, err)
|
|
}
|
|
if called {
|
|
t.Fatal("invalid request reached lookup implementation")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLookupDirectoryEntriesAcceptsMaximumBatch(t *testing.T) {
|
|
req := repeatedBatchLookupRequest(maxFilerBatchLookupRequests, &filer_pb.LookupDirectoryEntryRequest{
|
|
Directory: "/batch",
|
|
Name: "key",
|
|
})
|
|
response, err := lookupDirectoryEntries(context.Background(), req, 13,
|
|
func(context.Context, util.FullPath) (*filer.Entry, int64, error) {
|
|
return nil, 99, filer_pb.ErrNotFound
|
|
},
|
|
func(context.Context, []string) (map[string][]wdclient.Location, error) {
|
|
t.Fatal("volume lookup called for an all-miss batch")
|
|
return nil, nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("maximum-sized batch failed: %v", err)
|
|
}
|
|
if got := len(response.Results); got != maxFilerBatchLookupRequests {
|
|
t.Fatalf("result count = %d, want %d", got, maxFilerBatchLookupRequests)
|
|
}
|
|
for _, index := range []int{0, maxFilerBatchLookupRequests - 1} {
|
|
if got := response.Results[index]; got.LogTsNs != 99 || got.LogSignature != 13 {
|
|
t.Fatalf("miss fence[%d] = (%d,%d), want (99,13)", index, got.LogTsNs, got.LogSignature)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestLookupDirectoryEntriesUsesFilerSignatureForFoundAndMiss(t *testing.T) {
|
|
store := newRenameTestStore()
|
|
if err := store.InsertEntry(context.Background(), newFileEntry("/batch/found", 1)); err != nil {
|
|
t.Fatalf("seed entry: %v", err)
|
|
}
|
|
testFiler := newRenameTestFiler(t, store)
|
|
testFiler.Signature = 73
|
|
server := &FilerServer{
|
|
filer: testFiler,
|
|
entryLockTable: util.NewLockTable[util.FullPath](),
|
|
}
|
|
|
|
response, err := server.LookupDirectoryEntries(context.Background(), &filer_pb.LookupDirectoryEntriesRequest{
|
|
Requests: []*filer_pb.LookupDirectoryEntryRequest{
|
|
{Directory: "/batch", Name: "found"},
|
|
{Directory: "/batch", Name: "missing"},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("batch lookup: %v", err)
|
|
}
|
|
if !response.Results[0].Found || response.Results[1].Found {
|
|
t.Fatalf("unexpected found states: %v, %v", response.Results[0].Found, response.Results[1].Found)
|
|
}
|
|
for index, result := range response.Results {
|
|
if result.LogSignature != 73 || result.LogTsNs == 0 {
|
|
t.Fatalf("result[%d] fence = (%d,%d), want nonzero timestamp and signature 73", index, result.LogTsNs, result.LogSignature)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPopulateBatchReadAuthMintsExactFidCapabilities(t *testing.T) {
|
|
response := &filer_pb.LookupDirectoryEntriesResponse{Results: []*filer_pb.LookupDirectoryEntryResult{
|
|
{Found: true, Entry: &filer_pb.Entry{Chunks: []*filer_pb.FileChunk{
|
|
{FileId: "7,abc"}, {FileId: "8,def"}, {FileId: "7,abc"},
|
|
}}},
|
|
{Found: false},
|
|
}}
|
|
populateBatchReadAuth(response, func(fid string) string { return "jwt:" + fid })
|
|
if !reflect.DeepEqual(response.ReadAuth, map[string]string{
|
|
"7,abc": "jwt:7,abc",
|
|
"8,def": "jwt:8,def",
|
|
}) {
|
|
t.Fatalf("read auth = %#v", response.ReadAuth)
|
|
}
|
|
}
|
|
|
|
func TestPopulateBatchReadAuthDoesNotInventCapabilitiesWhenSigningIsDisabled(t *testing.T) {
|
|
response := &filer_pb.LookupDirectoryEntriesResponse{Results: []*filer_pb.LookupDirectoryEntryResult{
|
|
{Found: true, Entry: &filer_pb.Entry{Chunks: []*filer_pb.FileChunk{{FileId: "7,abc"}}}},
|
|
}}
|
|
populateBatchReadAuth(response, func(string) string { return "" })
|
|
if len(response.ReadAuth) != 0 {
|
|
t.Fatalf("read auth must be empty without a signing key: %#v", response.ReadAuth)
|
|
}
|
|
}
|
|
|
|
func TestLookupDirectoryEntriesPreservesOrderBoundsParallelismAndDeduplicatesVolumes(t *testing.T) {
|
|
const requestCount = 96
|
|
requests := make([]*filer_pb.LookupDirectoryEntryRequest, requestCount)
|
|
for index := range requests {
|
|
requests[index] = &filer_pb.LookupDirectoryEntryRequest{
|
|
Directory: "/batch",
|
|
Name: fmt.Sprintf("item-%03d", index),
|
|
}
|
|
}
|
|
|
|
var active atomic.Int32
|
|
var activeMax atomic.Int32
|
|
findEntry := func(_ context.Context, path util.FullPath) (*filer.Entry, int64, error) {
|
|
index, err := strconv.Atoi(strings.TrimPrefix(string(path), "/batch/item-"))
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
current := active.Add(1)
|
|
defer active.Add(-1)
|
|
for {
|
|
previous := activeMax.Load()
|
|
if current <= previous || activeMax.CompareAndSwap(previous, current) {
|
|
break
|
|
}
|
|
}
|
|
// Reverse completion order within each wave so append-based implementations fail.
|
|
time.Sleep(time.Duration(requestCount-index) * 50 * time.Microsecond)
|
|
if index == 7 {
|
|
return nil, int64(1000 + index), errors.New("lookup failed")
|
|
}
|
|
if index == 11 {
|
|
return nil, int64(1000 + index), filer_pb.ErrNotFound
|
|
}
|
|
volumeID := uint32(index%3 + 1)
|
|
chunk := &filer_pb.FileChunk{Fid: &filer_pb.FileId{VolumeId: volumeID, FileKey: uint64(index + 1), Cookie: 17}}
|
|
if index == 5 {
|
|
chunk = &filer_pb.FileChunk{FileId: "2,0294cbb9892b"}
|
|
}
|
|
return &filer.Entry{FullPath: path, Chunks: []*filer_pb.FileChunk{chunk, chunk}}, int64(1000 + index), nil
|
|
}
|
|
|
|
volumeLookupCalls := 0
|
|
var lookedUpVolumeIDs []string
|
|
lookupVolumes := func(_ context.Context, volumeIDs []string) (map[string][]wdclient.Location, error) {
|
|
volumeLookupCalls++
|
|
lookedUpVolumeIDs = append([]string(nil), volumeIDs...)
|
|
return map[string][]wdclient.Location{
|
|
"1": {{Url: "volume-1:8080"}},
|
|
"2": {{Url: "volume-2:8080"}},
|
|
"3": {{Url: "volume-3:8080"}},
|
|
}, nil
|
|
}
|
|
|
|
const logSignature = 0x1234
|
|
response, err := lookupDirectoryEntries(context.Background(), &filer_pb.LookupDirectoryEntriesRequest{Requests: requests}, logSignature, findEntry, lookupVolumes)
|
|
if err != nil {
|
|
t.Fatalf("batch lookup: %v", err)
|
|
}
|
|
if got := activeMax.Load(); got <= 1 || got > maxFilerBatchLookupWorkers {
|
|
t.Fatalf("parallel workers peaked at %d, want 2..%d", got, maxFilerBatchLookupWorkers)
|
|
}
|
|
if volumeLookupCalls != 1 {
|
|
t.Fatalf("volume lookup calls = %d, want 1", volumeLookupCalls)
|
|
}
|
|
if want := []string{"1", "2", "3"}; !reflect.DeepEqual(lookedUpVolumeIDs, want) {
|
|
t.Fatalf("volume IDs = %v, want %v", lookedUpVolumeIDs, want)
|
|
}
|
|
if got := len(response.LocationsMap); got != 3 {
|
|
t.Fatalf("location map size = %d, want 3", got)
|
|
}
|
|
|
|
for index, result := range response.Results {
|
|
if result.LogTsNs != int64(1000+index) || result.LogSignature != logSignature {
|
|
t.Fatalf("result[%d] fence = (%d,%d), want (%d,%d)", index, result.LogTsNs, result.LogSignature, 1000+index, logSignature)
|
|
}
|
|
switch index {
|
|
case 7:
|
|
if result.Found || result.Entry != nil || result.Error != "lookup failed" {
|
|
t.Fatalf("error result[%d] = %+v", index, result)
|
|
}
|
|
case 11:
|
|
if result.Found || result.Entry != nil || result.Error != "" {
|
|
t.Fatalf("not-found result[%d] = %+v", index, result)
|
|
}
|
|
default:
|
|
if !result.Found || result.Entry == nil || result.Error != "" {
|
|
t.Fatalf("found result[%d] = %+v", index, result)
|
|
}
|
|
if want := fmt.Sprintf("item-%03d", index); result.Entry.Name != want {
|
|
t.Fatalf("result[%d] name = %q, want %q", index, result.Entry.Name, want)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestLookupDirectoryEntriesMarksEntriesWithUnavailableVolumes(t *testing.T) {
|
|
response, err := lookupDirectoryEntries(context.Background(), &filer_pb.LookupDirectoryEntriesRequest{
|
|
Requests: []*filer_pb.LookupDirectoryEntryRequest{{Directory: "/batch", Name: "key"}},
|
|
}, 42, func(_ context.Context, path util.FullPath) (*filer.Entry, int64, error) {
|
|
return &filer.Entry{
|
|
FullPath: path,
|
|
Chunks: []*filer_pb.FileChunk{{
|
|
Fid: &filer_pb.FileId{VolumeId: 9, FileKey: 1, Cookie: 2},
|
|
}},
|
|
}, 123, nil
|
|
}, func(_ context.Context, volumeIDs []string) (map[string][]wdclient.Location, error) {
|
|
if !sort.StringsAreSorted(volumeIDs) {
|
|
t.Fatalf("volume IDs are not sorted: %v", volumeIDs)
|
|
}
|
|
return nil, errors.New("master unavailable")
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("batch lookup: %v", err)
|
|
}
|
|
result := response.Results[0]
|
|
if result.LogTsNs != 123 || result.LogSignature != 42 {
|
|
t.Fatalf("result fence = (%d,%d), want (123,42)", result.LogTsNs, result.LogSignature)
|
|
}
|
|
if !result.Found || result.Entry == nil {
|
|
t.Fatalf("metadata result lost when volume lookup failed: %+v", result)
|
|
}
|
|
if !strings.Contains(result.Error, "volume 9 has no locations") || !strings.Contains(result.Error, "master unavailable") {
|
|
t.Fatalf("result error = %q", result.Error)
|
|
}
|
|
locations, ok := response.LocationsMap["9"]
|
|
if !ok || locations == nil || len(locations.Locations) != 0 {
|
|
t.Fatalf("missing volume map entry = %#v, present=%v", locations, ok)
|
|
}
|
|
}
|
|
|
|
func TestLookupDirectoryEntriesKeepsMalformedEntryError(t *testing.T) {
|
|
response, err := lookupDirectoryEntries(context.Background(), &filer_pb.LookupDirectoryEntriesRequest{
|
|
Requests: []*filer_pb.LookupDirectoryEntryRequest{{Directory: "/batch", Name: "key"}},
|
|
}, 42, func(_ context.Context, path util.FullPath) (*filer.Entry, int64, error) {
|
|
return &filer.Entry{
|
|
FullPath: path,
|
|
Chunks: []*filer_pb.FileChunk{
|
|
{Fid: &filer_pb.FileId{VolumeId: 9, FileKey: 1, Cookie: 2}},
|
|
{FileId: "not-a-file-id"},
|
|
},
|
|
}, 123, nil
|
|
}, func(context.Context, []string) (map[string][]wdclient.Location, error) {
|
|
return map[string][]wdclient.Location{}, nil
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("batch lookup: %v", err)
|
|
}
|
|
result := response.Results[0]
|
|
if !result.Found || result.Entry == nil || !strings.Contains(result.Error, "invalid file id") {
|
|
t.Fatalf("malformed entry was reported as a clean miss: %+v", result)
|
|
}
|
|
}
|
|
|
|
func cacheVolumeEntry(_ context.Context, path util.FullPath) (*filer.Entry, int64, error) {
|
|
return &filer.Entry{
|
|
FullPath: path,
|
|
Chunks: []*filer_pb.FileChunk{{
|
|
Fid: &filer_pb.FileId{VolumeId: 9, FileKey: 1, Cookie: 2},
|
|
}},
|
|
}, 123, nil
|
|
}
|
|
|
|
func TestLookupDirectoryEntriesCanTreatRetiredCacheVolumeAsMiss(t *testing.T) {
|
|
response, err := lookupDirectoryEntries(context.Background(), &filer_pb.LookupDirectoryEntriesRequest{
|
|
Requests: []*filer_pb.LookupDirectoryEntryRequest{{Directory: "/batch", Name: "key"}},
|
|
UnavailableVolumeIsMiss: true,
|
|
}, 42, cacheVolumeEntry, func(_ context.Context, volumeIDs []string) (map[string][]wdclient.Location, error) {
|
|
// The master answered and left volume 9 out.
|
|
return map[string][]wdclient.Location{}, errors.New("volume 9: volume id 9 not found")
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("batch lookup: %v", err)
|
|
}
|
|
result := response.Results[0]
|
|
if result.Found || result.Entry != nil || result.Error != "" {
|
|
t.Fatalf("retired cache volume did not become a clean miss: %+v", result)
|
|
}
|
|
if result.LogTsNs != 123 || result.LogSignature != 42 {
|
|
t.Fatalf("miss fence = (%d,%d), want (123,42)", result.LogTsNs, result.LogSignature)
|
|
}
|
|
if _, exists := response.LocationsMap["9"]; exists {
|
|
t.Fatalf("cache miss returned an unusable location: %#v", response.LocationsMap["9"])
|
|
}
|
|
}
|
|
|
|
func TestLookupDirectoryEntriesKeepsCacheEntryWhenMasterIsUnreachable(t *testing.T) {
|
|
response, err := lookupDirectoryEntries(context.Background(), &filer_pb.LookupDirectoryEntriesRequest{
|
|
Requests: []*filer_pb.LookupDirectoryEntryRequest{{Directory: "/batch", Name: "key"}},
|
|
UnavailableVolumeIsMiss: true,
|
|
}, 42, cacheVolumeEntry, func(context.Context, []string) (map[string][]wdclient.Location, error) {
|
|
return nil, errors.New("master unavailable")
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("batch lookup: %v", err)
|
|
}
|
|
result := response.Results[0]
|
|
if !result.Found || result.Entry == nil {
|
|
t.Fatalf("unanswered lookup was reported as a miss: %+v", result)
|
|
}
|
|
if !strings.Contains(result.Error, "volume 9 has no locations") || !strings.Contains(result.Error, "master unavailable") {
|
|
t.Fatalf("result error = %q", result.Error)
|
|
}
|
|
locations, ok := response.LocationsMap["9"]
|
|
if !ok || locations == nil || len(locations.Locations) != 0 {
|
|
t.Fatalf("unresolved volume map entry = %#v, present=%v", locations, ok)
|
|
}
|
|
}
|
|
|
|
func TestLookupDirectoryEntriesKeepsMalformedEntryErrorInCacheMode(t *testing.T) {
|
|
response, err := lookupDirectoryEntries(context.Background(), &filer_pb.LookupDirectoryEntriesRequest{
|
|
Requests: []*filer_pb.LookupDirectoryEntryRequest{{Directory: "/batch", Name: "key"}},
|
|
UnavailableVolumeIsMiss: true,
|
|
}, 42, func(_ context.Context, path util.FullPath) (*filer.Entry, int64, error) {
|
|
return &filer.Entry{
|
|
FullPath: path,
|
|
Chunks: []*filer_pb.FileChunk{
|
|
{Fid: &filer_pb.FileId{VolumeId: 9, FileKey: 1, Cookie: 2}},
|
|
{FileId: "not-a-file-id"},
|
|
},
|
|
}, 123, nil
|
|
}, func(context.Context, []string) (map[string][]wdclient.Location, error) {
|
|
return map[string][]wdclient.Location{}, nil
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("batch lookup: %v", err)
|
|
}
|
|
result := response.Results[0]
|
|
if !result.Found || result.Entry == nil || !strings.Contains(result.Error, "invalid file id") {
|
|
t.Fatalf("malformed entry was reported as a clean miss: %+v", result)
|
|
}
|
|
}
|
|
|
|
func TestCacheBatchLookupSelectsAuthoritativeVolumeLocations(t *testing.T) {
|
|
cachedCalls, authoritativeCalls := 0, 0
|
|
cached := func(context.Context, []string) (map[string][]wdclient.Location, error) {
|
|
cachedCalls++
|
|
return map[string][]wdclient.Location{"9": {{Url: "retired-volume:8080"}}}, nil
|
|
}
|
|
authoritative := func(context.Context, []string) (map[string][]wdclient.Location, error) {
|
|
authoritativeCalls++
|
|
return nil, nil
|
|
}
|
|
|
|
lookup := selectFilerBatchVolumeLookup(&filer_pb.LookupDirectoryEntriesRequest{
|
|
UnavailableVolumeIsMiss: true,
|
|
}, cached, authoritative)
|
|
locations, err := lookup(context.Background(), []string{"9"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if cachedCalls != 0 || authoritativeCalls != 1 || len(locations) != 0 {
|
|
t.Fatalf("cache lookup used stale path: cached=%d authoritative=%d locations=%v", cachedCalls, authoritativeCalls, locations)
|
|
}
|
|
|
|
lookup = selectFilerBatchVolumeLookup(&filer_pb.LookupDirectoryEntriesRequest{}, cached, authoritative)
|
|
if _, err := lookup(context.Background(), []string{"9"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if cachedCalls != 1 || authoritativeCalls != 1 {
|
|
t.Fatalf("ordinary lookup did not keep cached path: cached=%d authoritative=%d", cachedCalls, authoritativeCalls)
|
|
}
|
|
}
|
|
|
|
func repeatedBatchLookupRequest(count int, request *filer_pb.LookupDirectoryEntryRequest) *filer_pb.LookupDirectoryEntriesRequest {
|
|
requests := make([]*filer_pb.LookupDirectoryEntryRequest, count)
|
|
for index := range requests {
|
|
requests[index] = &filer_pb.LookupDirectoryEntryRequest{
|
|
Directory: request.Directory,
|
|
Name: request.Name,
|
|
}
|
|
}
|
|
return &filer_pb.LookupDirectoryEntriesRequest{Requests: requests}
|
|
}
|