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  }