github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/swarmkit/remotes/remotes_test.go (about)

     1  package remotes
     2  
     3  import (
     4  	"math"
     5  	"testing"
     6  
     7  	"github.com/docker/swarmkit/api"
     8  )
     9  
    10  func TestRemotesSimple(t *testing.T) {
    11  	peers := []api.Peer{{Addr: "one"}, {Addr: "two"}, {Addr: "three"}}
    12  	remotes := NewRemotes(peers...)
    13  	index := remotes.Weights()
    14  
    15  	seen := make(map[api.Peer]int)
    16  	for i := 0; i < len(peers)*10; i++ {
    17  		next, err := remotes.Select()
    18  		if err != nil {
    19  			t.Fatalf("error selecting remote: %v", err)
    20  		}
    21  
    22  		if _, ok := index[next]; !ok {
    23  			t.Fatalf("unexpected remote returned: %q", next)
    24  		}
    25  		seen[next]++
    26  	}
    27  
    28  	for _, peer := range peers {
    29  		if _, ok := seen[peer]; !ok {
    30  			t.Fatalf("%q not returned after several selection attempts", peer)
    31  		}
    32  	}
    33  
    34  	weights := remotes.Weights()
    35  	var value int
    36  	for peer := range seen {
    37  		weight, ok := weights[peer]
    38  		if !ok {
    39  			t.Fatalf("unexpected remote returned: %v", peer)
    40  		}
    41  
    42  		if weight <= 0 {
    43  			t.Fatalf("weight should not be zero or less: %v (%v)", weight, remotes.Weights())
    44  		}
    45  
    46  		if value == 0 {
    47  			// sets benchmark weight, they should all be the same
    48  			value = weight
    49  			continue
    50  		}
    51  
    52  		if weight != value {
    53  			t.Fatalf("all weights should be same %q: %v != %v, %v", peer, weight, value, weights)
    54  		}
    55  	}
    56  }
    57  
    58  func TestRemotesEmpty(t *testing.T) {
    59  	remotes := NewRemotes()
    60  
    61  	_, err := remotes.Select()
    62  	if err != errRemotesUnavailable {
    63  		t.Fatalf("unexpected return from Select: %v", err)
    64  	}
    65  
    66  }
    67  
    68  func TestRemotesExclude(t *testing.T) {
    69  	peers := []api.Peer{{Addr: "one"}, {Addr: "two"}, {Addr: "three"}}
    70  	excludes := []string{"one", "two", "three"}
    71  	remotes := NewRemotes(peers...)
    72  
    73  	// exclude all
    74  	_, err := remotes.Select(excludes...)
    75  	if err != errRemotesUnavailable {
    76  		t.Fatal("select an excluded peer")
    77  	}
    78  
    79  	// exclude one peer
    80  	for i := 0; i < len(peers)*10; i++ {
    81  		next, err := remotes.Select(excludes[0])
    82  		if err != nil {
    83  			t.Fatalf("error selecting remote: %v", err)
    84  		}
    85  
    86  		if next == peers[0] {
    87  			t.Fatal("select an excluded peer")
    88  		}
    89  	}
    90  
    91  	// exclude 2 peers
    92  	for i := 0; i < len(peers)*10; i++ {
    93  		next, err := remotes.Select(excludes[1:]...)
    94  		if err != nil {
    95  			t.Fatalf("error selecting remote: %v", err)
    96  		}
    97  
    98  		if next != peers[0] {
    99  			t.Fatalf("select an excluded peer: %v", next)
   100  		}
   101  	}
   102  }
   103  
   104  // TestRemotesConvergence ensures that as we get positive observations,
   105  // the actual weight increases or converges to a value higher than the initial
   106  // value.
   107  func TestRemotesConvergence(t *testing.T) {
   108  	remotes := NewRemotes()
   109  	remotes.Observe(api.Peer{Addr: "one"}, DefaultObservationWeight)
   110  
   111  	// zero weighted against 1
   112  	if float64(remotes.Weights()[api.Peer{Addr: "one"}]) < remoteWeightSmoothingFactor {
   113  		t.Fatalf("unexpected weight: %v < %v", remotes.Weights()[api.Peer{Addr: "one"}], remoteWeightSmoothingFactor)
   114  	}
   115  
   116  	// crank it up
   117  	for i := 0; i < 10; i++ {
   118  		remotes.Observe(api.Peer{Addr: "one"}, DefaultObservationWeight)
   119  	}
   120  
   121  	if float64(remotes.Weights()[api.Peer{Addr: "one"}]) < remoteWeightSmoothingFactor {
   122  		t.Fatalf("did not converge towards 1: %v < %v", remotes.Weights()[api.Peer{Addr: "one"}], remoteWeightSmoothingFactor)
   123  	}
   124  
   125  	if remotes.Weights()[api.Peer{Addr: "one"}] > remoteWeightMax {
   126  		t.Fatalf("should never go over towards %v: %v > %v", remoteWeightMax, remotes.Weights()[api.Peer{Addr: "one"}], 1.0)
   127  	}
   128  
   129  	// provided a poor review
   130  	remotes.Observe(api.Peer{Addr: "one"}, -DefaultObservationWeight)
   131  
   132  	if remotes.Weights()[api.Peer{Addr: "one"}] > 0 {
   133  		t.Fatalf("should be below zero: %v", remotes.Weights()[api.Peer{Addr: "one"}])
   134  	}
   135  
   136  	// The remote should be heavily downweighted but not completely to -1
   137  	expected := (-remoteWeightSmoothingFactor + (1 - remoteWeightSmoothingFactor))
   138  	epsilon := -1e-5
   139  	if float64(remotes.Weights()[api.Peer{Addr: "one"}]) < expected+epsilon {
   140  		t.Fatalf("weight should not drop so quickly: %v < %v", remotes.Weights()[api.Peer{Addr: "one"}], expected)
   141  	}
   142  }
   143  
   144  func TestRemotesZeroWeights(t *testing.T) {
   145  	remotes := NewRemotes()
   146  	peers := []api.Peer{{Addr: "one"}, {Addr: "two"}, {Addr: "three"}}
   147  	for _, peer := range peers {
   148  		remotes.Observe(peer, 0)
   149  	}
   150  
   151  	seen := map[api.Peer]struct{}{}
   152  	for i := 0; i < 1000; i++ {
   153  		peer, err := remotes.Select()
   154  		if err != nil {
   155  			t.Fatalf("unexpected error from Select: %v", err)
   156  		}
   157  
   158  		seen[peer] = struct{}{}
   159  	}
   160  
   161  	for peer := range remotes.Weights() {
   162  		if _, ok := seen[peer]; !ok {
   163  			t.Fatalf("remote not returned after several tries: %v (seen: %v)", peer, seen)
   164  		}
   165  	}
   166  
   167  	// Pump up number 3!
   168  	remotes.Observe(api.Peer{Addr: "three"}, DefaultObservationWeight)
   169  
   170  	count := map[api.Peer]int{}
   171  	for i := 0; i < 100; i++ {
   172  		// basically, we expect the same one to return
   173  		peer, err := remotes.Select()
   174  		if err != nil {
   175  			t.Fatalf("unexpected error from Select: %v", err)
   176  		}
   177  
   178  		count[peer]++
   179  
   180  		// keep observing three
   181  		remotes.Observe(api.Peer{Addr: "three"}, DefaultObservationWeight)
   182  	}
   183  
   184  	// here, we ensure that three is at least three times more likely to be
   185  	// selected. This is somewhat arbitrary.
   186  	if count[api.Peer{Addr: "three"}] <= count[api.Peer{Addr: "one"}]*3 || count[api.Peer{Addr: "three"}] <= count[api.Peer{Addr: "two"}] {
   187  		t.Fatal("three should outpace one and two")
   188  	}
   189  }
   190  
   191  func TestRemotesLargeRanges(t *testing.T) {
   192  	peers := []api.Peer{{Addr: "one"}, {Addr: "two"}, {Addr: "three"}}
   193  	index := make(map[api.Peer]struct{}, len(peers))
   194  	remotes := NewRemotes(peers...)
   195  
   196  	for _, peer := range peers {
   197  		index[peer] = struct{}{}
   198  	}
   199  
   200  	remotes.Observe(peers[0], 0)
   201  	remotes.Observe(peers[1], math.MaxInt32)
   202  	remotes.Observe(peers[2], math.MinInt32)
   203  	remotes.Observe(peers[2], remoteWeightMax) // three bounces back!
   204  
   205  	seen := make(map[api.Peer]int)
   206  	for i := 0; i < len(peers)*remoteWeightMax*4; i++ {
   207  		next, err := remotes.Select()
   208  		if err != nil {
   209  			t.Fatalf("error selecting remote: %v", err)
   210  		}
   211  
   212  		if _, ok := index[next]; !ok {
   213  			t.Fatalf("unexpected remote returned: %q", next)
   214  		}
   215  		seen[next]++
   216  	}
   217  
   218  	for _, peer := range peers {
   219  		if _, ok := seen[peer]; !ok {
   220  			t.Fatalf("%q not returned after several selection attempts, %v", peer, remotes)
   221  		}
   222  	}
   223  
   224  	for peer := range seen {
   225  		if _, ok := index[peer]; !ok {
   226  			t.Fatalf("unexpected remote returned: %v", peer)
   227  		}
   228  	}
   229  }
   230  
   231  func TestRemotesDownweight(t *testing.T) {
   232  	peers := []api.Peer{{Addr: "one"}, {Addr: "two"}, {Addr: "three"}}
   233  	index := make(map[api.Peer]struct{}, len(peers))
   234  	remotes := NewRemotes(peers...)
   235  
   236  	for _, peer := range peers {
   237  		index[peer] = struct{}{}
   238  	}
   239  
   240  	for _, p := range peers {
   241  		remotes.Observe(p, DefaultObservationWeight)
   242  	}
   243  
   244  	remotes.Observe(peers[0], -DefaultObservationWeight)
   245  
   246  	samples := 100000
   247  	chosen := 0
   248  
   249  	for i := 0; i < samples; i++ {
   250  		p, err := remotes.Select()
   251  		if err != nil {
   252  			t.Fatalf("error selecting remote: %v", err)
   253  		}
   254  		if p == peers[0] {
   255  			chosen++
   256  		}
   257  	}
   258  	ratio := float32(chosen) / float32(samples)
   259  	t.Logf("ratio: %f", ratio)
   260  	if ratio > 0.001 {
   261  		t.Fatalf("downweighted peer is chosen too often, ratio: %f", ratio)
   262  	}
   263  }
   264  
   265  // TestRemotesPractical ensures that under a single poor observation, such as
   266  // an error, the likelihood of selecting the node dramatically decreases.
   267  func TestRemotesPractical(t *testing.T) {
   268  	peers := []api.Peer{{Addr: "one"}, {Addr: "two"}, {Addr: "three"}}
   269  	remotes := NewRemotes(peers...)
   270  	seen := map[api.Peer]int{}
   271  	selections := 1000
   272  	tolerance := 0.20 // allow 20% delta to reduce test failure probability
   273  
   274  	// set a baseline, where selections should be even
   275  	for i := 0; i < selections; i++ {
   276  		peer, err := remotes.Select()
   277  		if err != nil {
   278  			t.Fatalf("error selecting peer: %v", err)
   279  		}
   280  
   281  		remotes.Observe(peer, DefaultObservationWeight)
   282  		seen[peer]++
   283  	}
   284  
   285  	expected, delta := selections/len(peers), int(tolerance*float64(selections))
   286  	low, high := expected-delta, expected+delta
   287  	for peer, count := range seen {
   288  		if !(count >= low && count <= high) {
   289  			t.Fatalf("weighted selection not balanced: %v selected %v/%v, expected range %v, %v", peer, count, selections, low, high)
   290  		}
   291  	}
   292  
   293  	// one bad observation should mark the node as bad
   294  	remotes.Observe(peers[0], -DefaultObservationWeight)
   295  
   296  	seen = map[api.Peer]int{} // result
   297  	for i := 0; i < selections; i++ {
   298  		peer, err := remotes.Select()
   299  		if err != nil {
   300  			t.Fatalf("error selecting peer: %v", err)
   301  		}
   302  
   303  		seen[peer]++
   304  	}
   305  
   306  	tolerance = 0.10 // switch to 10% tolerance for two peers
   307  	// same check as above, with only 2 peers, the bad peer should be unseen
   308  	expected, delta = selections/(len(peers)-1), int(tolerance*float64(selections))
   309  	low, high = expected-delta, expected+delta
   310  	for peer, count := range seen {
   311  		if peer == peers[0] {
   312  			// we have an *extremely* low probability of selecting this node
   313  			// (like 0.005%) once. Selecting this more than a few times will
   314  			// fail the test.
   315  			if count > 3 {
   316  				t.Fatalf("downweighted peer should not be selected, selected %v times", count)
   317  			}
   318  		}
   319  
   320  		if !(count >= low && count <= high) {
   321  			t.Fatalf("weighted selection not balanced: %v selected %v/%v, expected range %v, %v", peer, count, selections, low, high)
   322  		}
   323  	}
   324  }
   325  
   326  var peers = []api.Peer{
   327  	{Addr: "one"}, {Addr: "two"}, {Addr: "three"},
   328  	{Addr: "four"}, {Addr: "five"}, {Addr: "six"},
   329  	{Addr: "seven0"}, {Addr: "eight0"}, {Addr: "nine0"},
   330  	{Addr: "seven1"}, {Addr: "eight1"}, {Addr: "nine1"},
   331  	{Addr: "seven2"}, {Addr: "eight2"}, {Addr: "nine2"},
   332  	{Addr: "seven3"}, {Addr: "eight3"}, {Addr: "nine3"},
   333  	{Addr: "seven4"}, {Addr: "eight4"}, {Addr: "nine4"},
   334  	{Addr: "seven5"}, {Addr: "eight5"}, {Addr: "nine5"},
   335  	{Addr: "seven6"}, {Addr: "eight6"}, {Addr: "nine6"}}
   336  
   337  func BenchmarkRemotesSelect3(b *testing.B) {
   338  	benchmarkRemotesSelect(b, peers[:3]...)
   339  }
   340  
   341  func BenchmarkRemotesSelect5(b *testing.B) {
   342  	benchmarkRemotesSelect(b, peers[:5]...)
   343  }
   344  
   345  func BenchmarkRemotesSelect9(b *testing.B) {
   346  	benchmarkRemotesSelect(b, peers[:9]...)
   347  }
   348  
   349  func BenchmarkRemotesSelect27(b *testing.B) {
   350  	benchmarkRemotesSelect(b, peers[:27]...)
   351  }
   352  
   353  func benchmarkRemotesSelect(b *testing.B, peers ...api.Peer) {
   354  	remotes := NewRemotes(peers...)
   355  
   356  	for i := 0; i < b.N; i++ {
   357  		_, err := remotes.Select()
   358  		if err != nil {
   359  			b.Fatalf("error selecting remote: %v", err)
   360  		}
   361  	}
   362  }
   363  
   364  func BenchmarkRemotesObserve3(b *testing.B) {
   365  	benchmarkRemotesObserve(b, peers[:3]...)
   366  }
   367  
   368  func BenchmarkRemotesObserve5(b *testing.B) {
   369  	benchmarkRemotesObserve(b, peers[:5]...)
   370  }
   371  
   372  func BenchmarkRemotesObserve9(b *testing.B) {
   373  	benchmarkRemotesObserve(b, peers[:9]...)
   374  }
   375  
   376  func BenchmarkRemotesObserve27(b *testing.B) {
   377  	benchmarkRemotesObserve(b, peers[:27]...)
   378  }
   379  
   380  func benchmarkRemotesObserve(b *testing.B, peers ...api.Peer) {
   381  	remotes := NewRemotes(peers...)
   382  
   383  	for i := 0; i < b.N; i++ {
   384  		remotes.Observe(peers[i%len(peers)], DefaultObservationWeight)
   385  	}
   386  }