tests: Unify TestCompactionHash and extend it to also Delete keys and Defrag

Signed-off-by: Marek Siarkowicz <siarkowicz@google.com>
This commit is contained in:
Marek Siarkowicz
2022-06-08 14:55:53 +02:00
parent 00bc8da0ef
commit 037a898ba0
4 changed files with 207 additions and 165 deletions

View File

@@ -15,6 +15,7 @@
package mvcc package mvcc
import ( import (
"context"
"fmt" "fmt"
"testing" "testing"
@@ -22,14 +23,10 @@ import (
"go.etcd.io/etcd/pkg/v3/traceutil" "go.etcd.io/etcd/pkg/v3/traceutil"
"go.etcd.io/etcd/server/v3/lease" "go.etcd.io/etcd/server/v3/lease"
betesting "go.etcd.io/etcd/server/v3/mvcc/backend/testing" betesting "go.etcd.io/etcd/server/v3/mvcc/backend/testing"
"go.etcd.io/etcd/server/v3/storage/mvcc/testutil"
"go.uber.org/zap/zaptest" "go.uber.org/zap/zaptest"
) )
const (
// Use high prime
compactionCycle = 71
)
// Test HashByRevValue values to ensure we don't change the output which would // Test HashByRevValue values to ensure we don't change the output which would
// have catastrophic consequences. Expected output is just hardcoded, so please // have catastrophic consequences. Expected output is just hardcoded, so please
// regenerate it every time you change input parameters. // regenerate it every time you change input parameters.
@@ -39,12 +36,12 @@ func TestHashByRevValue(t *testing.T) {
var totalRevisions int64 = 1210 var totalRevisions int64 = 1210
assert.Less(t, int64(s.cfg.CompactionBatchLimit), totalRevisions) assert.Less(t, int64(s.cfg.CompactionBatchLimit), totalRevisions)
assert.Less(t, int64(compactionCycle*10), totalRevisions) assert.Less(t, int64(testutil.CompactionCycle*10), totalRevisions)
var rev int64 var rev int64
var got []KeyValueHash var got []KeyValueHash
for ; rev < totalRevisions; rev += compactionCycle { for ; rev < totalRevisions; rev += testutil.CompactionCycle {
putKVs(s, rev, compactionCycle) putKVs(s, rev, testutil.CompactionCycle)
hash := testHashByRev(t, s, rev+compactionCycle/2) hash := testHashByRev(t, s, rev+testutil.CompactionCycle/2)
got = append(got, hash) got = append(got, hash)
} }
putKVs(s, rev, totalRevisions) putKVs(s, rev, totalRevisions)
@@ -79,11 +76,11 @@ func TestHashByRevValueLastRevision(t *testing.T) {
var totalRevisions int64 = 1210 var totalRevisions int64 = 1210
assert.Less(t, int64(s.cfg.CompactionBatchLimit), totalRevisions) assert.Less(t, int64(s.cfg.CompactionBatchLimit), totalRevisions)
assert.Less(t, int64(compactionCycle*10), totalRevisions) assert.Less(t, int64(testutil.CompactionCycle*10), totalRevisions)
var rev int64 var rev int64
var got []KeyValueHash var got []KeyValueHash
for ; rev < totalRevisions; rev += compactionCycle { for ; rev < totalRevisions; rev += testutil.CompactionCycle {
putKVs(s, rev, compactionCycle) putKVs(s, rev, testutil.CompactionCycle)
hash := testHashByRev(t, s, 0) hash := testHashByRev(t, s, 0)
got = append(got, hash) got = append(got, hash)
} }
@@ -115,7 +112,7 @@ func TestHashByRevValueLastRevision(t *testing.T) {
func putKVs(s *store, rev, count int64) { func putKVs(s *store, rev, count int64) {
for i := rev; i <= rev+count; i++ { for i := rev; i <= rev+count; i++ {
s.Put([]byte(pickKey(i)), []byte(fmt.Sprint(i)), 0) s.Put([]byte(testutil.PickKey(i)), []byte(fmt.Sprint(i)), 0)
} }
} }
@@ -135,57 +132,43 @@ func TestCompactionHash(t *testing.T) {
b, _ := betesting.NewDefaultTmpBackend(t) b, _ := betesting.NewDefaultTmpBackend(t)
s := NewStore(zaptest.NewLogger(t), b, &lease.FakeLessor{}, StoreConfig{}) s := NewStore(zaptest.NewLogger(t), b, &lease.FakeLessor{}, StoreConfig{})
var totalRevisions int64 = 1210 testutil.TestCompactionHash(context.Background(), t, hashTestCase{s}, s.cfg.CompactionBatchLimit)
assert.Less(t, int64(s.cfg.CompactionBatchLimit), totalRevisions)
assert.Less(t, int64(compactionCycle*10), totalRevisions)
var rev int64
for ; rev < totalRevisions; rev += compactionCycle {
testCompactionHash(t, s, rev, rev+compactionCycle)
}
testCompactionHash(t, s, rev, rev+totalRevisions)
} }
func testCompactionHash(t *testing.T, s *store, start, stop int64) { type hashTestCase struct {
for i := start; i <= stop; i++ { *store
s.Put([]byte(pickKey(i)), []byte(fmt.Sprint(i)), 0)
}
hash1, _, err := s.hashByRev(stop)
assert.NoError(t, err, "error on rev %v", stop)
_, prevCompactRev, err := s.updateCompactRev(stop)
assert.NoError(t, err, "error on rev %v", stop)
hash2, err := s.scheduleCompaction(stop, prevCompactRev)
assert.NoError(t, err, "error on rev %v", stop)
assert.Equal(t, hash1, hash2, "hashes do not match on rev %v", stop)
} }
func pickKey(i int64) string { func (tc hashTestCase) Put(ctx context.Context, key, value string) error {
if i%(compactionCycle*2) == 30 { tc.store.Put([]byte(key), []byte(value), 0)
return "zenek" return nil
} }
if i%compactionCycle == 30 {
return "xavery" func (tc hashTestCase) Delete(ctx context.Context, key string) error {
tc.store.DeleteRange([]byte(key), nil)
return nil
} }
// Use low prime number to ensure repeats without alignment
switch i % 7 { func (tc hashTestCase) HashByRev(ctx context.Context, rev int64) (testutil.KeyValueHash, error) {
case 0: hash, _, err := tc.store.HashStorage().HashByRev(rev)
return "alice" return testutil.KeyValueHash{Hash: hash.Hash, CompactRevision: hash.CompactRevision, Revision: hash.Revision}, err
case 1:
return "bob"
case 2:
return "celine"
case 3:
return "dominik"
case 4:
return "eve"
case 5:
return "frederica"
case 6:
return "gorge"
default:
panic("Can't count")
} }
func (tc hashTestCase) Defrag(ctx context.Context) error {
return tc.store.b.Defrag()
}
func (tc hashTestCase) Compact(ctx context.Context, rev int64) error {
done, err := tc.store.Compact(traceutil.TODO(), rev)
if err != nil {
return err
}
select {
case <-done:
case <-ctx.Done():
return ctx.Err()
}
return nil
} }
func TestHasherStore(t *testing.T) { func TestHasherStore(t *testing.T) {

View File

@@ -0,0 +1,105 @@
// Copyright 2022 The etcd Authors
//
// 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 testutil
import (
"context"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
const (
// CompactionCycle is high prime used to test hash calculation between compactions.
CompactionCycle = 71
)
func TestCompactionHash(ctx context.Context, t *testing.T, h CompactionHashTestCase, compactionBatchLimit int) {
var totalRevisions int64 = 1210
assert.Less(t, int64(compactionBatchLimit), totalRevisions)
assert.Less(t, int64(CompactionCycle*10), totalRevisions)
var rev int64
for ; rev < totalRevisions; rev += CompactionCycle {
testCompactionHash(ctx, t, h, rev, rev+CompactionCycle)
}
testCompactionHash(ctx, t, h, rev, rev+totalRevisions)
}
type CompactionHashTestCase interface {
Put(ctx context.Context, key, value string) error
Delete(ctx context.Context, key string) error
HashByRev(ctx context.Context, rev int64) (KeyValueHash, error)
Defrag(ctx context.Context) error
Compact(ctx context.Context, rev int64) error
}
type KeyValueHash struct {
Hash uint32
CompactRevision int64
Revision int64
}
func testCompactionHash(ctx context.Context, t *testing.T, h CompactionHashTestCase, start, stop int64) {
for i := start; i <= stop; i++ {
if i%67 == 0 {
err := h.Delete(ctx, PickKey(i+83))
assert.NoError(t, err, "error on delete")
} else {
err := h.Put(ctx, PickKey(i), fmt.Sprint(i))
assert.NoError(t, err, "error on put")
}
}
hash1, err := h.HashByRev(ctx, stop)
assert.NoError(t, err, "error on hash (rev %v)", stop)
err = h.Compact(ctx, stop)
assert.NoError(t, err, "error on compact (rev %v)", stop)
err = h.Defrag(ctx)
assert.NoError(t, err, "error on defrag")
hash2, err := h.HashByRev(ctx, stop)
assert.NoError(t, err, "error on hash (rev %v)", stop)
assert.Equal(t, hash1, hash2, "hashes do not match on rev %v", stop)
}
func PickKey(i int64) string {
if i%(CompactionCycle*2) == 30 {
return "zenek"
}
if i%CompactionCycle == 30 {
return "xavery"
}
// Use low prime number to ensure repeats without alignment
switch i % 7 {
case 0:
return "alice"
case 1:
return "bob"
case 2:
return "celine"
case 3:
return "dominik"
case 4:
return "eve"
case 5:
return "frederica"
case 6:
return "gorge"
default:
panic("Can't count")
}
}

View File

@@ -25,7 +25,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert" "go.etcd.io/etcd/server/v3/storage/mvcc/testutil"
"go.uber.org/zap/zaptest" "go.uber.org/zap/zaptest"
"google.golang.org/grpc" "google.golang.org/grpc"
@@ -37,11 +37,6 @@ import (
"go.etcd.io/etcd/tests/v3/integration" "go.etcd.io/etcd/tests/v3/integration"
) )
const (
// Use high prime
compactionCycle = 71
)
func TestMaintenanceHashKV(t *testing.T) { func TestMaintenanceHashKV(t *testing.T) {
integration.BeforeTest(t) integration.BeforeTest(t)
@@ -75,72 +70,51 @@ func TestMaintenanceHashKV(t *testing.T) {
} }
} }
// TODO: Change this to fuzz test
func TestCompactionHash(t *testing.T) { func TestCompactionHash(t *testing.T) {
integration.BeforeTest(t) integration.BeforeTest(t)
clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 1}) clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 1})
defer clus.Terminate(t) defer clus.Terminate(t)
ctx := context.Background()
cc, err := clus.ClusterClient() cc, err := clus.ClusterClient()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
var totalRevisions int64 = 1210 testutil.TestCompactionHash(context.Background(), t, hashTestCase{cc, clus.Members[0].GRPCURL()}, 1000)
assert.Less(t, int64(1000), totalRevisions)
assert.Less(t, int64(compactionCycle*10), totalRevisions)
var rev int64
for ; rev < totalRevisions; rev += compactionCycle {
testCompactionHash(ctx, t, cc, clus.Members[0].GRPCURL(), rev, rev+compactionCycle)
}
testCompactionHash(ctx, t, cc, clus.Members[0].GRPCURL(), rev, rev+totalRevisions)
} }
func testCompactionHash(ctx context.Context, t *testing.T, cc *clientv3.Client, url string, start, stop int64) { type hashTestCase struct {
for i := start; i <= stop; i++ { *clientv3.Client
cc.Put(ctx, pickKey(i), fmt.Sprint(i)) url string
} }
hash1, err := cc.HashKV(ctx, url, stop)
assert.NoError(t, err, "error on rev %v", stop)
_, err = cc.Compact(ctx, stop) func (tc hashTestCase) Put(ctx context.Context, key, value string) error {
assert.NoError(t, err, "error on compact rev %v", stop) _, err := tc.Client.Put(ctx, key, value)
return err
}
func (tc hashTestCase) Delete(ctx context.Context, key string) error {
_, err := tc.Client.Delete(ctx, key)
return err
}
func (tc hashTestCase) HashByRev(ctx context.Context, rev int64) (testutil.KeyValueHash, error) {
resp, err := tc.Client.HashKV(ctx, tc.url, rev)
return testutil.KeyValueHash{Hash: resp.Hash, CompactRevision: resp.CompactRevision, Revision: resp.Header.Revision}, err
}
func (tc hashTestCase) Defrag(ctx context.Context) error {
_, err := tc.Client.Defragment(ctx, tc.url)
return err
}
func (tc hashTestCase) Compact(ctx context.Context, rev int64) error {
_, err := tc.Client.Compact(ctx, rev)
// Wait for compaction to be compacted // Wait for compaction to be compacted
time.Sleep(50 * time.Millisecond) time.Sleep(50 * time.Millisecond)
return err
hash2, err := cc.HashKV(ctx, url, stop)
assert.NoError(t, err, "error on rev %v", stop)
assert.Equal(t, hash1, hash2, "hashes do not match on rev %v", stop)
}
func pickKey(i int64) string {
if i%(compactionCycle*2) == 30 {
return "zenek"
}
if i%compactionCycle == 30 {
return "xavery"
}
// Use low prime number to ensure repeats without alignment
switch i % 7 {
case 0:
return "alice"
case 1:
return "bob"
case 2:
return "celine"
case 3:
return "dominik"
case 4:
return "eve"
case 5:
return "frederica"
case 6:
return "gorge"
default:
panic("Can't count")
}
} }
func TestMaintenanceMoveLeader(t *testing.T) { func TestMaintenanceMoveLeader(t *testing.T) {

View File

@@ -16,15 +16,14 @@ package integration
import ( import (
"context" "context"
"fmt"
"net" "net"
"net/http" "net/http"
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert"
clientv3 "go.etcd.io/etcd/client/v3" clientv3 "go.etcd.io/etcd/client/v3"
"go.etcd.io/etcd/server/v3/etcdserver" "go.etcd.io/etcd/server/v3/etcdserver"
"go.etcd.io/etcd/server/v3/storage/mvcc/testutil"
) )
const ( const (
@@ -32,22 +31,18 @@ const (
compactionCycle = 71 compactionCycle = 71
) )
func TestCompactionHashHTTP(t *testing.T) { // TODO: Change this to fuzz test
func TestCompactionHash(t *testing.T) {
BeforeTest(t) BeforeTest(t)
clus := NewClusterV3(t, &ClusterConfig{Size: 1}) clus := NewClusterV3(t, &ClusterConfig{Size: 1})
defer clus.Terminate(t) defer clus.Terminate(t)
ctx := context.Background()
cc, err := clus.ClusterClient() cc, err := clus.ClusterClient()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
var totalRevisions int64 = 1210
assert.Less(t, int64(1000), totalRevisions)
assert.Less(t, int64(compactionCycle*10), totalRevisions)
var rev int64
client := &http.Client{ client := &http.Client{
Transport: &http.Transport{ Transport: &http.Transport{
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
@@ -55,54 +50,39 @@ func TestCompactionHashHTTP(t *testing.T) {
}, },
}, },
} }
for ; rev < totalRevisions; rev += compactionCycle {
testCompactionHash(ctx, t, cc, client, rev, rev+compactionCycle) testutil.TestCompactionHash(context.Background(), t, hashTestCase{cc, clus.Members[0].GRPCURL(), client}, 1000)
}
testCompactionHash(ctx, t, cc, client, rev, rev+totalRevisions)
} }
func testCompactionHash(ctx context.Context, t *testing.T, cc *clientv3.Client, client *http.Client, start, stop int64) { type hashTestCase struct {
for i := start; i <= stop; i++ { *clientv3.Client
cc.Put(ctx, pickKey(i), fmt.Sprint(i)) url string
http *http.Client
} }
hash1, err := etcdserver.HashByRev(ctx, client, "http://unix", stop)
assert.NoError(t, err, "error on rev %v", stop)
_, err = cc.Compact(ctx, stop) func (tc hashTestCase) Put(ctx context.Context, key, value string) error {
assert.NoError(t, err, "error on compact rev %v", stop) _, err := tc.Client.Put(ctx, key, value)
return err
}
func (tc hashTestCase) Delete(ctx context.Context, key string) error {
_, err := tc.Client.Delete(ctx, key)
return err
}
func (tc hashTestCase) HashByRev(ctx context.Context, rev int64) (testutil.KeyValueHash, error) {
resp, err := etcdserver.HashByRev(ctx, tc.http, "http://unix", rev)
return testutil.KeyValueHash{Hash: resp.Hash, CompactRevision: resp.CompactRevision, Revision: resp.Header.Revision}, err
}
func (tc hashTestCase) Defrag(ctx context.Context) error {
_, err := tc.Client.Defragment(ctx, tc.url)
return err
}
func (tc hashTestCase) Compact(ctx context.Context, rev int64) error {
_, err := tc.Client.Compact(ctx, rev)
// Wait for compaction to be compacted // Wait for compaction to be compacted
time.Sleep(50 * time.Millisecond) time.Sleep(50 * time.Millisecond)
return err
hash2, err := etcdserver.HashByRev(ctx, client, "http://unix", stop)
assert.NoError(t, err, "error on rev %v", stop)
assert.Equal(t, hash1, hash2, "hashes do not match on rev %v", stop)
}
func pickKey(i int64) string {
if i%(compactionCycle*2) == 30 {
return "zenek"
}
if i%compactionCycle == 30 {
return "xavery"
}
// Use low prime number to ensure repeats without alignment
switch i % 7 {
case 0:
return "alice"
case 1:
return "bob"
case 2:
return "celine"
case 3:
return "dominik"
case 4:
return "eve"
case 5:
return "frederica"
case 6:
return "gorge"
default:
panic("Can't count")
}
} }