github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/go-control-plane/pkg/cache/v3/linear_test.go (about) 1 // Copyright 2020 Envoyproxy Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package cache 16 17 import ( 18 "errors" 19 "fmt" 20 "reflect" 21 "testing" 22 23 "github.com/stretchr/testify/require" 24 25 wrappers "github.com/golang/protobuf/ptypes/wrappers" 26 27 endpoint "github.com/hxx258456/ccgo/go-control-plane/envoy/config/endpoint/v3" 28 "github.com/hxx258456/ccgo/go-control-plane/pkg/cache/types" 29 "github.com/hxx258456/ccgo/go-control-plane/pkg/server/stream/v3" 30 ) 31 32 const ( 33 testType = "google.protobuf.StringValue" 34 ) 35 36 func testResource(s string) types.Resource { 37 return &wrappers.StringValue{Value: s} 38 } 39 40 func verifyResponse(t *testing.T, ch <-chan Response, version string, num int) { 41 t.Helper() 42 r := <-ch 43 if r.GetRequest().TypeUrl != testType { 44 t.Errorf("unexpected empty request type URL: %q", r.GetRequest().TypeUrl) 45 } 46 out, err := r.GetDiscoveryResponse() 47 if err != nil { 48 t.Fatal(err) 49 } 50 if out.VersionInfo == "" { 51 t.Error("unexpected response empty version") 52 } 53 if n := len(out.Resources); n != num { 54 t.Errorf("unexpected number of responses: got %d, want %d", n, num) 55 } 56 if version != "" && out.VersionInfo != version { 57 t.Errorf("unexpected version: got %q, want %q", out.VersionInfo, version) 58 } 59 if out.TypeUrl != testType { 60 t.Errorf("unexpected type URL: %q", out.TypeUrl) 61 } 62 } 63 64 type resourceInfo struct { 65 name string 66 version string 67 } 68 69 func verifyDeltaResponse(t *testing.T, ch <-chan DeltaResponse, resources []resourceInfo, deleted []string) { 70 t.Helper() 71 r := <-ch 72 if r.GetDeltaRequest().TypeUrl != testType { 73 t.Errorf("unexpected empty request type URL: %q", r.GetDeltaRequest().TypeUrl) 74 } 75 out, err := r.GetDeltaDiscoveryResponse() 76 if err != nil { 77 t.Fatal(err) 78 } 79 if len(out.Resources) != len(resources) { 80 t.Errorf("unexpected number of responses: got %d, want %d", len(out.Resources), len(resources)) 81 } 82 for _, r := range resources { 83 found := false 84 for _, r1 := range out.Resources { 85 if r1.Name == r.name && r1.Version == r.version { 86 found = true 87 break 88 } else if r1.Name == r.name { 89 t.Errorf("unexpected version for resource %q: got %q, want %q", r.name, r1.Version, r.version) 90 found = true 91 break 92 } 93 } 94 if !found { 95 t.Errorf("resource with name %q not found in response", r.name) 96 } 97 } 98 if out.TypeUrl != testType { 99 t.Errorf("unexpected type URL: %q", out.TypeUrl) 100 } 101 if len(out.RemovedResources) != len(deleted) { 102 t.Errorf("unexpected number of removed resurces: got %d, want %d", len(out.RemovedResources), len(deleted)) 103 } 104 for _, r := range deleted { 105 found := false 106 for _, rr := range out.RemovedResources { 107 if r == rr { 108 found = true 109 break 110 } 111 } 112 if !found { 113 t.Errorf("Expected resource %s to be deleted", r) 114 } 115 } 116 } 117 118 func checkWatchCount(t *testing.T, c *LinearCache, name string, count int) { 119 t.Helper() 120 if i := c.NumWatches(name); i != count { 121 t.Errorf("unexpected number of watches for %q: got %d, want %d", name, i, count) 122 } 123 } 124 125 func checkDeltaWatchCount(t *testing.T, c *LinearCache, count int) { 126 t.Helper() 127 if i := c.NumDeltaWatches(); i != count { 128 t.Errorf("unexpected number of delta watches: got %d, want %d", i, count) 129 } 130 } 131 132 func mustBlock(t *testing.T, w <-chan Response) { 133 select { 134 case <-w: 135 t.Error("watch must block") 136 default: 137 } 138 } 139 140 func mustBlockDelta(t *testing.T, w <-chan DeltaResponse) { 141 select { 142 case <-w: 143 t.Error("watch must block") 144 default: 145 } 146 } 147 148 func hashResource(t *testing.T, resource types.Resource) string { 149 marshaledResource, err := MarshalResource(resource) 150 if err != nil { 151 t.Fatal(err) 152 } 153 v := HashResource(marshaledResource) 154 if v == "" { 155 t.Fatal(errors.New("failed to build resource version")) 156 } 157 return v 158 } 159 160 func TestLinearInitialResources(t *testing.T) { 161 c := NewLinearCache(testType, WithInitialResources(map[string]types.Resource{"a": testResource("a"), "b": testResource("b")})) 162 w := make(chan Response, 1) 163 c.CreateWatch(&Request{ResourceNames: []string{"a"}, TypeUrl: testType}, w) 164 verifyResponse(t, w, "0", 1) 165 c.CreateWatch(&Request{TypeUrl: testType}, w) 166 verifyResponse(t, w, "0", 2) 167 } 168 169 func TestLinearCornerCases(t *testing.T) { 170 c := NewLinearCache(testType) 171 err := c.UpdateResource("a", nil) 172 if err == nil { 173 t.Error("expected error on nil resource") 174 } 175 // create an incorrect type URL request 176 w := make(chan Response, 1) 177 c.CreateWatch(&Request{TypeUrl: "test"}, w) 178 select { 179 case r := <-w: 180 if r != nil { 181 t.Error("response should be nil") 182 } 183 default: 184 t.Error("should receive nil response") 185 } 186 } 187 188 func TestLinearBasic(t *testing.T) { 189 c := NewLinearCache(testType) 190 191 // Create watches before a resource is ready 192 w1 := make(chan Response, 1) 193 c.CreateWatch(&Request{ResourceNames: []string{"a"}, TypeUrl: testType, VersionInfo: "0"}, w1) 194 mustBlock(t, w1) 195 196 w := make(chan Response, 1) 197 c.CreateWatch(&Request{TypeUrl: testType, VersionInfo: "0"}, w) 198 mustBlock(t, w) 199 checkWatchCount(t, c, "a", 2) 200 checkWatchCount(t, c, "b", 1) 201 require.NoError(t, c.UpdateResource("a", testResource("a"))) 202 checkWatchCount(t, c, "a", 0) 203 checkWatchCount(t, c, "b", 0) 204 verifyResponse(t, w1, "1", 1) 205 verifyResponse(t, w, "1", 1) 206 207 // Request again, should get same response 208 c.CreateWatch(&Request{ResourceNames: []string{"a"}, TypeUrl: testType, VersionInfo: "0"}, w) 209 checkWatchCount(t, c, "a", 0) 210 verifyResponse(t, w, "1", 1) 211 c.CreateWatch(&Request{TypeUrl: testType, VersionInfo: "0"}, w) 212 checkWatchCount(t, c, "a", 0) 213 verifyResponse(t, w, "1", 1) 214 215 // Add another element and update the first, response should be different 216 require.NoError(t, c.UpdateResource("b", testResource("b"))) 217 require.NoError(t, c.UpdateResource("a", testResource("aa"))) 218 c.CreateWatch(&Request{ResourceNames: []string{"a"}, TypeUrl: testType, VersionInfo: "0"}, w) 219 verifyResponse(t, w, "3", 1) 220 c.CreateWatch(&Request{TypeUrl: testType, VersionInfo: "0"}, w) 221 verifyResponse(t, w, "3", 2) 222 } 223 224 func TestLinearSetResources(t *testing.T) { 225 c := NewLinearCache(testType) 226 227 // Create new resources 228 w1 := make(chan Response, 1) 229 c.CreateWatch(&Request{ResourceNames: []string{"a"}, TypeUrl: testType, VersionInfo: "0"}, w1) 230 mustBlock(t, w1) 231 w2 := make(chan Response, 1) 232 c.CreateWatch(&Request{TypeUrl: testType, VersionInfo: "0"}, w2) 233 mustBlock(t, w2) 234 c.SetResources(map[string]types.Resource{ 235 "a": testResource("a"), 236 "b": testResource("b"), 237 }) 238 verifyResponse(t, w1, "1", 1) 239 verifyResponse(t, w2, "1", 2) // the version was only incremented once for all resources 240 241 // Add another element and update the first, response should be different 242 c.CreateWatch(&Request{ResourceNames: []string{"a"}, TypeUrl: testType, VersionInfo: "1"}, w1) 243 mustBlock(t, w1) 244 c.CreateWatch(&Request{TypeUrl: testType, VersionInfo: "1"}, w2) 245 mustBlock(t, w2) 246 c.SetResources(map[string]types.Resource{ 247 "a": testResource("aa"), 248 "b": testResource("b"), 249 "c": testResource("c"), 250 }) 251 verifyResponse(t, w1, "2", 1) 252 verifyResponse(t, w2, "2", 3) 253 254 // Delete resource 255 c.CreateWatch(&Request{ResourceNames: []string{"a"}, TypeUrl: testType, VersionInfo: "2"}, w1) 256 mustBlock(t, w1) 257 c.CreateWatch(&Request{TypeUrl: testType, VersionInfo: "2"}, w2) 258 mustBlock(t, w2) 259 c.SetResources(map[string]types.Resource{ 260 "b": testResource("b"), 261 "c": testResource("c"), 262 }) 263 verifyResponse(t, w1, "", 0) // removing a resource from the set triggers existing watches for deleted resources 264 verifyResponse(t, w2, "3", 2) 265 } 266 267 func TestLinearGetResources(t *testing.T) { 268 c := NewLinearCache(testType) 269 270 expectedResources := map[string]types.Resource{ 271 "a": testResource("a"), 272 "b": testResource("b"), 273 } 274 275 c.SetResources(expectedResources) 276 277 resources := c.GetResources() 278 279 if !reflect.DeepEqual(expectedResources, resources) { 280 t.Errorf("resources are not equal. got: %v want: %v", resources, expectedResources) 281 } 282 } 283 284 func TestLinearVersionPrefix(t *testing.T) { 285 c := NewLinearCache(testType, WithVersionPrefix("instance1-")) 286 287 w := make(chan Response, 1) 288 c.CreateWatch(&Request{ResourceNames: []string{"a"}, TypeUrl: testType, VersionInfo: "0"}, w) 289 verifyResponse(t, w, "instance1-0", 0) 290 291 require.NoError(t, c.UpdateResource("a", testResource("a"))) 292 c.CreateWatch(&Request{ResourceNames: []string{"a"}, TypeUrl: testType, VersionInfo: "0"}, w) 293 verifyResponse(t, w, "instance1-1", 1) 294 295 c.CreateWatch(&Request{ResourceNames: []string{"a"}, TypeUrl: testType, VersionInfo: "instance1-1"}, w) 296 mustBlock(t, w) 297 checkWatchCount(t, c, "a", 1) 298 } 299 300 func TestLinearDeletion(t *testing.T) { 301 c := NewLinearCache(testType, WithInitialResources(map[string]types.Resource{"a": testResource("a"), "b": testResource("b")})) 302 w := make(chan Response, 1) 303 c.CreateWatch(&Request{ResourceNames: []string{"a"}, TypeUrl: testType, VersionInfo: "0"}, w) 304 mustBlock(t, w) 305 checkWatchCount(t, c, "a", 1) 306 require.NoError(t, c.DeleteResource("a")) 307 verifyResponse(t, w, "1", 0) 308 checkWatchCount(t, c, "a", 0) 309 c.CreateWatch(&Request{TypeUrl: testType, VersionInfo: "0"}, w) 310 verifyResponse(t, w, "1", 1) 311 checkWatchCount(t, c, "b", 0) 312 require.NoError(t, c.DeleteResource("b")) 313 c.CreateWatch(&Request{TypeUrl: testType, VersionInfo: "1"}, w) 314 verifyResponse(t, w, "2", 0) 315 checkWatchCount(t, c, "b", 0) 316 } 317 318 func TestLinearWatchTwo(t *testing.T) { 319 c := NewLinearCache(testType, WithInitialResources(map[string]types.Resource{"a": testResource("a"), "b": testResource("b")})) 320 w := make(chan Response, 1) 321 c.CreateWatch(&Request{ResourceNames: []string{"a", "b"}, TypeUrl: testType, VersionInfo: "0"}, w) 322 mustBlock(t, w) 323 w1 := make(chan Response, 1) 324 c.CreateWatch(&Request{TypeUrl: testType, VersionInfo: "0"}, w1) 325 mustBlock(t, w1) 326 require.NoError(t, c.UpdateResource("a", testResource("aa"))) 327 // should only get the modified resource 328 verifyResponse(t, w, "1", 1) 329 verifyResponse(t, w1, "1", 2) 330 } 331 332 func TestLinearCancel(t *testing.T) { 333 c := NewLinearCache(testType) 334 require.NoError(t, c.UpdateResource("a", testResource("a"))) 335 336 // cancel watch-all 337 w := make(chan Response, 1) 338 cancel := c.CreateWatch(&Request{TypeUrl: testType, VersionInfo: "1"}, w) 339 mustBlock(t, w) 340 checkWatchCount(t, c, "a", 1) 341 cancel() 342 checkWatchCount(t, c, "a", 0) 343 344 // cancel watch for "a" 345 cancel = c.CreateWatch(&Request{ResourceNames: []string{"a"}, TypeUrl: testType, VersionInfo: "1"}, w) 346 mustBlock(t, w) 347 checkWatchCount(t, c, "a", 1) 348 cancel() 349 checkWatchCount(t, c, "a", 0) 350 351 // open four watches for "a" and "b" and two for all, cancel one of each, make sure the second one is unaffected 352 w2 := make(chan Response, 1) 353 w3 := make(chan Response, 1) 354 w4 := make(chan Response, 1) 355 cancel = c.CreateWatch(&Request{ResourceNames: []string{"a"}, TypeUrl: testType, VersionInfo: "1"}, w) 356 cancel2 := c.CreateWatch(&Request{ResourceNames: []string{"b"}, TypeUrl: testType, VersionInfo: "1"}, w2) 357 cancel3 := c.CreateWatch(&Request{TypeUrl: testType, VersionInfo: "1"}, w3) 358 cancel4 := c.CreateWatch(&Request{TypeUrl: testType, VersionInfo: "1"}, w4) 359 mustBlock(t, w) 360 mustBlock(t, w2) 361 mustBlock(t, w3) 362 mustBlock(t, w4) 363 checkWatchCount(t, c, "a", 3) 364 checkWatchCount(t, c, "b", 3) 365 cancel() 366 checkWatchCount(t, c, "a", 2) 367 checkWatchCount(t, c, "b", 3) 368 cancel3() 369 checkWatchCount(t, c, "a", 1) 370 checkWatchCount(t, c, "b", 2) 371 cancel2() 372 cancel4() 373 checkWatchCount(t, c, "a", 0) 374 checkWatchCount(t, c, "b", 0) 375 } 376 377 // TODO(mattklein123): This test requires GOMAXPROCS or -parallel >= 100. This should be 378 // rewritten to not require that. This is not the case in the GH actions environment. 379 func TestLinearConcurrentSetWatch(t *testing.T) { 380 c := NewLinearCache(testType) 381 n := 50 382 for i := 0; i < 2*n; i++ { 383 func(i int) { 384 t.Run(fmt.Sprintf("worker%d", i), func(t *testing.T) { 385 t.Parallel() 386 id := fmt.Sprintf("%d", i) 387 if i%2 == 0 { 388 t.Logf("update resource %q", id) 389 require.NoError(t, c.UpdateResource(id, testResource(id))) 390 } else { 391 id2 := fmt.Sprintf("%d", i-1) 392 t.Logf("request resources %q and %q", id, id2) 393 value := make(chan Response, 1) 394 c.CreateWatch(&Request{ 395 // Only expect one to become stale 396 ResourceNames: []string{id, id2}, 397 VersionInfo: "0", 398 TypeUrl: testType, 399 }, value) 400 // wait until all updates apply 401 verifyResponse(t, value, "", 1) 402 } 403 }) 404 }(i) 405 } 406 } 407 408 func TestLinearDeltaWildcard(t *testing.T) { 409 c := NewLinearCache(testType) 410 state1 := stream.NewStreamState(true, map[string]string{}) 411 w1 := make(chan DeltaResponse, 1) 412 c.CreateDeltaWatch(&DeltaRequest{TypeUrl: testType}, state1, w1) 413 mustBlockDelta(t, w1) 414 state2 := stream.NewStreamState(true, map[string]string{}) 415 w2 := make(chan DeltaResponse, 1) 416 c.CreateDeltaWatch(&DeltaRequest{TypeUrl: testType}, state2, w2) 417 mustBlockDelta(t, w1) 418 checkDeltaWatchCount(t, c, 2) 419 420 a := &endpoint.ClusterLoadAssignment{ClusterName: "a"} 421 hash := hashResource(t, a) 422 c.UpdateResource("a", a) 423 checkDeltaWatchCount(t, c, 0) 424 verifyDeltaResponse(t, w1, []resourceInfo{{"a", hash}}, nil) 425 verifyDeltaResponse(t, w2, []resourceInfo{{"a", hash}}, nil) 426 } 427 428 func TestLinearDeltaExistingResources(t *testing.T) { 429 c := NewLinearCache(testType) 430 a := &endpoint.ClusterLoadAssignment{ClusterName: "a"} 431 hashA := hashResource(t, a) 432 c.UpdateResource("a", a) 433 b := &endpoint.ClusterLoadAssignment{ClusterName: "b"} 434 hashB := hashResource(t, b) 435 c.UpdateResource("b", b) 436 437 state := stream.NewStreamState(false, map[string]string{"b": "", "c": ""}) // watching b and c - not interested in a 438 w := make(chan DeltaResponse, 1) 439 c.CreateDeltaWatch(&DeltaRequest{TypeUrl: testType}, state, w) 440 checkDeltaWatchCount(t, c, 0) 441 verifyDeltaResponse(t, w, []resourceInfo{{"b", hashB}}, []string{"c"}) 442 443 state = stream.NewStreamState(false, map[string]string{"a": "", "b": ""}) 444 w = make(chan DeltaResponse, 1) 445 c.CreateDeltaWatch(&DeltaRequest{TypeUrl: testType}, state, w) 446 checkDeltaWatchCount(t, c, 0) 447 verifyDeltaResponse(t, w, []resourceInfo{{"b", hashB}, {"a", hashA}}, nil) 448 } 449 450 func TestLinearDeltaInitialResourcesVersionSet(t *testing.T) { 451 c := NewLinearCache(testType) 452 a := &endpoint.ClusterLoadAssignment{ClusterName: "a"} 453 hashA := hashResource(t, a) 454 c.UpdateResource("a", a) 455 b := &endpoint.ClusterLoadAssignment{ClusterName: "b"} 456 hashB := hashResource(t, b) 457 c.UpdateResource("b", b) 458 459 state := stream.NewStreamState(false, map[string]string{"a": "", "b": hashB}) 460 w := make(chan DeltaResponse, 1) 461 c.CreateDeltaWatch(&DeltaRequest{TypeUrl: testType}, state, w) 462 checkDeltaWatchCount(t, c, 0) 463 verifyDeltaResponse(t, w, []resourceInfo{{"a", hashA}}, nil) // b is up to date and shouldn't be returned 464 465 state = stream.NewStreamState(false, map[string]string{"a": hashA, "b": hashB}) 466 w = make(chan DeltaResponse, 1) 467 c.CreateDeltaWatch(&DeltaRequest{TypeUrl: testType}, state, w) 468 mustBlockDelta(t, w) 469 checkDeltaWatchCount(t, c, 1) 470 b = &endpoint.ClusterLoadAssignment{ClusterName: "b", Endpoints: []*endpoint.LocalityLbEndpoints{{Priority: 10}}} // new version of b 471 hashB = hashResource(t, b) 472 c.UpdateResource("b", b) 473 checkDeltaWatchCount(t, c, 0) 474 verifyDeltaResponse(t, w, []resourceInfo{{"b", hashB}}, nil) 475 } 476 477 func TestLinearDeltaResourceUpdate(t *testing.T) { 478 c := NewLinearCache(testType) 479 a := &endpoint.ClusterLoadAssignment{ClusterName: "a"} 480 hashA := hashResource(t, a) 481 c.UpdateResource("a", a) 482 b := &endpoint.ClusterLoadAssignment{ClusterName: "b"} 483 hashB := hashResource(t, b) 484 c.UpdateResource("b", b) 485 486 state := stream.NewStreamState(false, map[string]string{"a": "", "b": ""}) 487 w := make(chan DeltaResponse, 1) 488 c.CreateDeltaWatch(&DeltaRequest{TypeUrl: testType}, state, w) 489 checkDeltaWatchCount(t, c, 0) 490 verifyDeltaResponse(t, w, []resourceInfo{{"b", hashB}, {"a", hashA}}, nil) 491 492 state = stream.NewStreamState(false, map[string]string{"a": hashA, "b": hashB}) 493 w = make(chan DeltaResponse, 1) 494 c.CreateDeltaWatch(&DeltaRequest{TypeUrl: testType}, state, w) 495 mustBlockDelta(t, w) 496 checkDeltaWatchCount(t, c, 1) 497 498 a = &endpoint.ClusterLoadAssignment{ClusterName: "a", Endpoints: []*endpoint.LocalityLbEndpoints{ //resource update 499 {Priority: 10}, 500 }} 501 hashA = hashResource(t, a) 502 c.UpdateResource("a", a) 503 verifyDeltaResponse(t, w, []resourceInfo{{"a", hashA}}, nil) 504 } 505 506 func TestLinearDeltaResourceDelete(t *testing.T) { 507 c := NewLinearCache(testType) 508 a := &endpoint.ClusterLoadAssignment{ClusterName: "a"} 509 hashA := hashResource(t, a) 510 c.UpdateResource("a", a) 511 b := &endpoint.ClusterLoadAssignment{ClusterName: "b"} 512 hashB := hashResource(t, b) 513 c.UpdateResource("b", b) 514 515 state := stream.NewStreamState(false, map[string]string{"a": "", "b": ""}) 516 w := make(chan DeltaResponse, 1) 517 c.CreateDeltaWatch(&DeltaRequest{TypeUrl: testType}, state, w) 518 checkDeltaWatchCount(t, c, 0) 519 verifyDeltaResponse(t, w, []resourceInfo{{"b", hashB}, {"a", hashA}}, nil) 520 521 state = stream.NewStreamState(false, map[string]string{"a": hashA, "b": hashB}) 522 w = make(chan DeltaResponse, 1) 523 c.CreateDeltaWatch(&DeltaRequest{TypeUrl: testType}, state, w) 524 mustBlockDelta(t, w) 525 checkDeltaWatchCount(t, c, 1) 526 527 a = &endpoint.ClusterLoadAssignment{ClusterName: "a", Endpoints: []*endpoint.LocalityLbEndpoints{ //resource update 528 {Priority: 10}, 529 }} 530 hashA = hashResource(t, a) 531 c.SetResources(map[string]types.Resource{"a": a}) 532 verifyDeltaResponse(t, w, []resourceInfo{{"a", hashA}}, []string{"b"}) 533 }