github.com/m3db/m3@v1.5.0/src/cluster/placement/algo/sharded_prop_test.go (about)

     1  // Copyright (c) 2021 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package algo
    22  
    23  import (
    24  	"fmt"
    25  	"math"
    26  	"os"
    27  	"testing"
    28  	"time"
    29  
    30  	"github.com/leanovate/gopter"
    31  	"github.com/leanovate/gopter/gen"
    32  	"github.com/leanovate/gopter/prop"
    33  
    34  	"github.com/m3db/m3/src/cluster/placement"
    35  )
    36  
    37  const minSuccessfulTests = 100
    38  
    39  func TestInitialPlacementIsBalancedPropTest(t *testing.T) {
    40  	var (
    41  		parameters = gopter.DefaultTestParameters()
    42  		seed       = time.Now().UnixNano()
    43  		props      = gopter.NewProperties(parameters)
    44  		reporter   = gopter.NewFormatedReporter(true, 160, os.Stdout)
    45  	)
    46  
    47  	parameters.MinSuccessfulTests = minSuccessfulTests
    48  	parameters.Rng.Seed(seed)
    49  
    50  	props.Property("Initial placement is balanced", prop.ForAll(
    51  		testInitialPlacementIsBalanced,
    52  		gen.IntRange(1, 3),
    53  		gen.IntRange(1, 20),
    54  		gen.IntRange(64, 3072),
    55  	))
    56  
    57  	if !props.Run(reporter) {
    58  		t.Errorf("failed with initial seed: %d", seed)
    59  	}
    60  }
    61  
    62  func testInitialPlacementIsBalanced(replicaCount, instanceCount, shardCount int) (bool, error) {
    63  	instances := make([]placement.Instance, 0)
    64  	for i := 0; i < replicaCount; i++ {
    65  		for j := 0; j < instanceCount; j++ {
    66  			var (
    67  				instanceID     = fmt.Sprintf("instance-%d-%03d", i, j)
    68  				isolationGroup = fmt.Sprintf("iso-%d", i)
    69  				instance       = placement.NewEmptyInstance(instanceID, isolationGroup, "zone", "endpoint", 1)
    70  			)
    71  			instances = append(instances, instance)
    72  		}
    73  	}
    74  
    75  	shardIDs := make([]uint32, shardCount)
    76  	for i := range shardIDs {
    77  		shardIDs[i] = uint32(i)
    78  	}
    79  
    80  	algo := newShardedAlgorithm(placement.NewOptions())
    81  	p, err := algo.InitialPlacement(instances, shardIDs, replicaCount)
    82  	if err != nil {
    83  		return false, err
    84  	}
    85  
    86  	for _, shardID := range shardIDs {
    87  		if n := len(p.InstancesForShard(shardID)); n != replicaCount {
    88  			return false, fmt.Errorf("shard %d has %d replicas, but replication factor is %d", shardID, n, replicaCount)
    89  		}
    90  	}
    91  
    92  	var (
    93  		min = math.MaxInt32
    94  		max = math.MinInt32
    95  	)
    96  	for _, instance := range p.Instances() {
    97  		n := instance.Shards().NumShards()
    98  		if n < min {
    99  			min = n
   100  		}
   101  		if n > max {
   102  			max = n
   103  		}
   104  	}
   105  	if max-min > 1 {
   106  		return false, fmt.Errorf("shard count differs by more than 1, min=%v max=%v", min, max)
   107  	}
   108  	return true, nil
   109  }