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  }