mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-08-28 10:54:59 +00:00
We disable fsync more consistently in testing, including using etcd's already-existing UnsafeNoFsync option to disable fsyncs in the backing store boltdb used by etcd, to reduce runtime of our tests on MacOS significantly. Corresponding to this, we update etcd by one patch to pick up a locally-invented patch which turns out to be nearly-identical to the upstream fix for "disabling fsync makes boltdb not even bother to write some data sometimes", which caused crashes galore.
214 lines
6.5 KiB
Go
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/molecula/featurebase/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, NoSync: true})
|
|
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)
|
|
}
|
|
}
|