featurebase/idalloc_test.go
Seebs 1045268f01 use testhook to ensure temporary files and directories are cleaned up
In nearly all cases, we can just switch ioutil.TempDir->testhook.TempDir
and similarly for TempFile. There's one case where we can't because we
need files to be removed before tests are over.

Also in the process give identifiable names to a lot of temporary files
and make sure they're being cleaned up, and don't use "/tmp/foo" as a
file name in a test that could be running in more than one test process
at once. :)
2021-03-11 19:42:10 -06:00

214 lines
6.5 KiB
Go

// Copyright 2020 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pilosa
import (
"crypto/rand"
"io"
"reflect"
"testing"
"time"
"github.com/pilosa/pilosa/v2/testhook"
bolt "go.etcd.io/bbolt"
)
func TestIDAlloc(t *testing.T) {
// Acquire a temporary file.
f, err := testhook.TempFile(t, "idalloc")
if err != nil {
t.Errorf("acquiring temporary file: %v", err)
return
}
defer func() {
cerr := f.Close()
if cerr != nil {
t.Errorf("closing temporary file: %v", cerr)
}
}()
// Open bolt.
db, err := bolt.Open(f.Name(), 0666, &bolt.Options{Timeout: 1 * time.Second})
if err != nil {
t.Errorf("opening bolt: %v", err)
return
}
defer func() {
cerr := db.Close()
if cerr != nil {
t.Errorf("closing bolt: %v", cerr)
}
}()
alloc := idAllocator{db: db}
a := IDAllocKey{
Index: "h",
Key: "a",
}
b := IDAllocKey{
Index: "h",
Key: "b",
}
var sessions [11][32]byte
for i := range sessions {
_, err = io.ReadFull(rand.Reader, sessions[i][:])
if err != nil {
t.Errorf("failed to obtain entropy: %v", err)
return
}
}
// Reserve 2 batches of IDs and then commit both.
if ranges, err := alloc.reserve(a, sessions[0], ^uint64(0), 3); err != nil {
t.Errorf("failed to reserve 3 IDs: %v", err)
} else {
expect := []IDRange{{1, 3}}
if !reflect.DeepEqual(expect, ranges) {
t.Errorf("expeced %v but got %v", expect, ranges)
}
}
if ranges, err := alloc.reserve(b, sessions[1], ^uint64(0), 3); err != nil {
t.Errorf("failed to reserve 3 IDs: %v", err)
} else {
expect := []IDRange{{4, 6}}
if !reflect.DeepEqual(expect, ranges) {
t.Errorf("expeced %v but got %v", expect, ranges)
}
}
if err := alloc.commit(a, sessions[0], 3); err != nil {
t.Errorf("failed to commit reservation of 3 IDs: %v", err)
}
if err := alloc.commit(b, sessions[1], 3); err != nil {
t.Errorf("failed to commit reservation of 3 IDs: %v", err)
}
// Test collision handling.
if ranges, err := alloc.reserve(a, sessions[2], ^uint64(0), 4); err != nil {
t.Errorf("failed to reserve 4 IDs: %v", err)
} else {
expect := []IDRange{{7, 10}}
if !reflect.DeepEqual(expect, ranges) {
t.Errorf("expeced %v but got %v", expect, ranges)
}
}
if ranges, err := alloc.reserve(a, sessions[3], ^uint64(0), 4); err != nil {
t.Errorf("failed to reserve 4 IDs: %v", err)
} else {
// Instead of reserving new ranges, we have taken over the previous reservation.
expect := []IDRange{{7, 10}}
if !reflect.DeepEqual(expect, ranges) {
t.Errorf("expeced %v but got %v", expect, ranges)
}
}
if err := alloc.commit(a, sessions[2], 4); err == nil {
// This was taken over by reservation 3, so this should have warned us that something went wrong.
t.Error("unexpected commit success for clobbered reservation")
}
if err := alloc.commit(a, sessions[3], 4); err != nil {
t.Errorf("failed to commit reservation of 4 IDs: %v", err)
}
// Test key return.
if ranges, err := alloc.reserve(a, sessions[4], ^uint64(0), 100); err != nil {
t.Errorf("failed to reserve 100 IDs: %v", err)
} else {
// Instead of reserving new ranges, we have taken over the previous reservation.
expect := []IDRange{{11, 110}}
if !reflect.DeepEqual(expect, ranges) {
t.Errorf("expeced %v but got %v", expect, ranges)
}
}
if err := alloc.commit(a, sessions[4], 1); err != nil {
t.Errorf("failed to commit reservation of 1 ID: %v", err)
}
if ranges, err := alloc.reserve(a, sessions[5], ^uint64(0), 100); err != nil {
t.Errorf("failed to reserve 100 IDs: %v", err)
} else {
// Instead of reserving new ranges, we have taken over the previous reservation.
expect := []IDRange{{12, 111}}
if !reflect.DeepEqual(expect, ranges) {
t.Errorf("expeced %v but got %v", expect, ranges)
}
}
if err := alloc.commit(a, sessions[5], 100); err != nil {
t.Errorf("failed to commit reservation of 100 IDs: %v", err)
}
// Test offset-based allocation.
p1 := IDAllocKey{
Index: "xyzzy",
Key: "kafka-partition-1",
}
p2 := IDAllocKey{
Index: "xyzzy",
Key: "kafka-partition-2",
}
// Start processsing on 2 partitions.
if ranges, err := alloc.reserve(p1, sessions[6], 0, 3); err != nil {
t.Errorf("failed to reserve 3 IDs: %v", err)
} else {
expect := []IDRange{{1, 3}}
if !reflect.DeepEqual(expect, ranges) {
t.Errorf("expeced %v but got %v", expect, ranges)
}
}
if ranges, err := alloc.reserve(p2, sessions[7], 0, 3); err != nil {
t.Errorf("failed to reserve 3 IDs: %v", err)
} else {
expect := []IDRange{{4, 6}}
if !reflect.DeepEqual(expect, ranges) {
t.Errorf("expeced %v but got %v", expect, ranges)
}
}
// Partition 2 finishes a batch of 2 IDs.
if err := alloc.commit(p2, sessions[7], 2); err != nil {
t.Errorf("failed to commit reservation of 2 IDs: %v", err)
}
// Partition 2 reserves more IDs.
if ranges, err := alloc.reserve(p2, sessions[8], 2, 3); err != nil {
t.Errorf("failed to reserve 3 IDs: %v", err)
} else {
// This could be coalesced here, but that is not true in the general case.
expect := []IDRange{{6, 6}, {7, 8}}
if !reflect.DeepEqual(expect, ranges) {
t.Errorf("expeced %v but got %v", expect, ranges)
}
}
// Partition 1 ingester failed and was restarted.
if ranges, err := alloc.reserve(p1, sessions[9], 0, 3); err != nil {
t.Errorf("failed to reserve 3 IDs: %v", err)
} else {
expect := []IDRange{{1, 3}}
if !reflect.DeepEqual(expect, ranges) {
t.Errorf("expeced %v but got %v", expect, ranges)
}
}
// This time, the partition 1 ingester finishes the batch.
// It commits the offset to Kafka but fails to commit to the ID allocator.
// It is restarted at Kafka's next offset.
if ranges, err := alloc.reserve(p1, sessions[10], 3, 4); err != nil {
t.Errorf("failed to reserve 3 IDs: %v", err)
} else {
expect := []IDRange{{9, 12}}
if !reflect.DeepEqual(expect, ranges) {
t.Errorf("expeced %v but got %v", expect, ranges)
}
}
// Partition 1 finishes a batch of 4 IDs.
if err := alloc.commit(p1, sessions[10], 4); err != nil {
t.Errorf("failed to commit reservation of 4 IDs: %v", err)
}
}