github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvserver/below_raft_protos_test.go (about) 1 // Copyright 2016 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package kvserver_test 12 13 import ( 14 "fmt" 15 "hash/fnv" 16 "math/rand" 17 "reflect" 18 "testing" 19 20 "github.com/cockroachdb/cockroach/pkg/kv/kvserver/kvserverpb" 21 "github.com/cockroachdb/cockroach/pkg/roachpb" 22 "github.com/cockroachdb/cockroach/pkg/storage" 23 "github.com/cockroachdb/cockroach/pkg/storage/enginepb" 24 "github.com/cockroachdb/cockroach/pkg/util/leaktest" 25 "github.com/cockroachdb/cockroach/pkg/util/protoutil" 26 "go.etcd.io/etcd/raft/raftpb" 27 ) 28 29 func verifyHash(b []byte, expectedSum uint64) error { 30 hash := fnv.New64a() 31 if _, err := hash.Write(b); err != nil { 32 return err 33 } 34 if sum := hash.Sum64(); sum != expectedSum { 35 return fmt.Errorf("expected sum %d; got %d", expectedSum, sum) 36 } 37 return nil 38 } 39 40 // An arbitrary number chosen to seed the PRNGs used to populate the tested 41 // protos. 42 const goldenSeed = 1337 43 44 // The count of randomly populated protos that will be concatenated and hashed 45 // per proto type. Given that the population functions have a chance of leaving 46 // some fields zero-valued, this number must be greater than `1` to give this 47 // test a reasonable chance of encountering a non-zero value of every field. 48 const itersPerProto = 20 49 50 type fixture struct { 51 populatedConstructor func(*rand.Rand) protoutil.Message 52 emptySum, populatedSum uint64 53 } 54 55 var belowRaftGoldenProtos = map[reflect.Type]fixture{ 56 reflect.TypeOf(&enginepb.MVCCMetadata{}): { 57 populatedConstructor: func(r *rand.Rand) protoutil.Message { 58 m := enginepb.NewPopulatedMVCCMetadata(r, false) 59 m.Txn = nil // never populated below Raft 60 return m 61 }, 62 emptySum: 7551962144604783939, 63 populatedSum: 12720006657210437557, 64 }, 65 reflect.TypeOf(&enginepb.RangeAppliedState{}): { 66 populatedConstructor: func(r *rand.Rand) protoutil.Message { 67 return enginepb.NewPopulatedRangeAppliedState(r, false) 68 }, 69 emptySum: 615555020845646359, 70 populatedSum: 6873743885651366543, 71 }, 72 reflect.TypeOf(&raftpb.HardState{}): { 73 populatedConstructor: func(r *rand.Rand) protoutil.Message { 74 type expectedHardState struct { 75 Term uint64 76 Vote uint64 77 Commit uint64 78 XXX_unrecognized []byte 79 } 80 // Conversion fails if new fields are added to `HardState`, in which case this method 81 // and the expected sums should be updated. 82 var _ = expectedHardState(raftpb.HardState{}) 83 84 n := r.Uint64() 85 return &raftpb.HardState{ 86 Term: n % 3, 87 Vote: n % 7, 88 Commit: n % 11, 89 XXX_unrecognized: nil, 90 } 91 }, 92 emptySum: 13621293256077144893, 93 populatedSum: 13375098491754757572, 94 }, 95 reflect.TypeOf(&kvserverpb.Liveness{}): { 96 populatedConstructor: func(r *rand.Rand) protoutil.Message { 97 return kvserverpb.NewPopulatedLiveness(r, false) 98 }, 99 emptySum: 892800390935990883, 100 populatedSum: 16231745342114354146, 101 }, 102 // This is used downstream of Raft only to write it into unreplicated keyspace 103 // as part of VersionUnreplicatedRaftTruncatedState. 104 // However, it has been sent through Raft for a long time, as part of 105 // ReplicatedEvalResult. 106 reflect.TypeOf(&roachpb.RaftTruncatedState{}): { 107 populatedConstructor: func(r *rand.Rand) protoutil.Message { 108 return roachpb.NewPopulatedRaftTruncatedState(r, false) 109 }, 110 emptySum: 5531676819244041709, 111 populatedSum: 14781226418259198098, 112 }, 113 } 114 115 func init() { 116 if storage.DefaultStorageEngine != enginepb.EngineTypeRocksDB { 117 // These are marshaled below Raft by the Pebble merge operator. The Pebble 118 // merge operator can be called below Raft whenever a Pebble Iterator is 119 // used. Note that we only see these protos marshaled below Raft when the 120 // engine type is not RocksDB. If the engine type is RocksDB the marshaling 121 // occurs in C++ which is invisible to the tracking mechanism. 122 belowRaftGoldenProtos[reflect.TypeOf(&roachpb.InternalTimeSeriesData{})] = fixture{ 123 populatedConstructor: func(r *rand.Rand) protoutil.Message { 124 return roachpb.NewPopulatedInternalTimeSeriesData(r, false) 125 }, 126 emptySum: 5531676819244041709, 127 populatedSum: 8911200268508796945, 128 } 129 belowRaftGoldenProtos[reflect.TypeOf(&enginepb.MVCCMetadataSubsetForMergeSerialization{})] = 130 fixture{ 131 populatedConstructor: func(r *rand.Rand) protoutil.Message { 132 return enginepb.NewPopulatedMVCCMetadataSubsetForMergeSerialization(r, false) 133 }, 134 emptySum: 14695981039346656037, 135 populatedSum: 7432412240713840291, 136 } 137 } 138 } 139 140 func TestBelowRaftProtos(t *testing.T) { 141 defer leaktest.AfterTest(t)() 142 143 // Enable the additional checks in TestMain. NB: running this test by itself 144 // will fail those extra checks - such failures are safe to ignore, so long 145 // as this test passes when run with the entire package's tests. 146 verifyBelowRaftProtos = true 147 148 slice := make([]byte, 1<<20) 149 for typ, fix := range belowRaftGoldenProtos { 150 if b, err := protoutil.Marshal(reflect.New(typ.Elem()).Interface().(protoutil.Message)); err != nil { 151 t.Fatal(err) 152 } else if err := verifyHash(b, fix.emptySum); err != nil { 153 t.Errorf("%s (empty): %s\n", typ, err) 154 } 155 156 randGen := rand.New(rand.NewSource(goldenSeed)) 157 158 bytes := slice 159 numBytes := 0 160 for i := 0; i < itersPerProto; i++ { 161 if n, err := fix.populatedConstructor(randGen).MarshalTo(bytes); err != nil { 162 t.Fatal(err) 163 } else { 164 bytes = bytes[n:] 165 numBytes += n 166 } 167 } 168 if err := verifyHash(slice[:numBytes], fix.populatedSum); err != nil { 169 t.Errorf("%s (populated): %s\n", typ, err) 170 } 171 } 172 }