github.com/kaisenlinux/docker@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 }