github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/go-control-plane/pkg/cache/v3/simple_test.go (about)

     1  // Copyright 2018 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_test
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"reflect"
    21  	"sync"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/stretchr/testify/assert"
    26  
    27  	core "github.com/hxx258456/ccgo/go-control-plane/envoy/config/core/v3"
    28  	discovery "github.com/hxx258456/ccgo/go-control-plane/envoy/service/discovery/v3"
    29  	"github.com/hxx258456/ccgo/go-control-plane/pkg/cache/types"
    30  	"github.com/hxx258456/ccgo/go-control-plane/pkg/cache/v3"
    31  	rsrc "github.com/hxx258456/ccgo/go-control-plane/pkg/resource/v3"
    32  	"github.com/hxx258456/ccgo/go-control-plane/pkg/test/resource/v3"
    33  )
    34  
    35  type group struct{}
    36  
    37  const (
    38  	key = "node"
    39  )
    40  
    41  func (group) ID(node *core.Node) string {
    42  	if node != nil {
    43  		return node.Id
    44  	}
    45  	return key
    46  }
    47  
    48  var (
    49  	version  = "x"
    50  	version2 = "y"
    51  
    52  	snapshot, _ = cache.NewSnapshot(version, map[rsrc.Type][]types.Resource{
    53  		rsrc.EndpointType:        {testEndpoint},
    54  		rsrc.ClusterType:         {testCluster},
    55  		rsrc.RouteType:           {testRoute},
    56  		rsrc.ListenerType:        {testListener},
    57  		rsrc.RuntimeType:         {testRuntime},
    58  		rsrc.SecretType:          {testSecret[0]},
    59  		rsrc.ExtensionConfigType: {testExtensionConfig},
    60  	})
    61  
    62  	ttl                = 2 * time.Second
    63  	snapshotWithTTL, _ = cache.NewSnapshotWithTTLs(version, map[rsrc.Type][]types.ResourceWithTTL{
    64  		rsrc.EndpointType:        {{Resource: testEndpoint, TTL: &ttl}},
    65  		rsrc.ClusterType:         {{Resource: testCluster}},
    66  		rsrc.RouteType:           {{Resource: testRoute}},
    67  		rsrc.ListenerType:        {{Resource: testListener}},
    68  		rsrc.RuntimeType:         {{Resource: testRuntime}},
    69  		rsrc.SecretType:          {{Resource: testSecret[0]}},
    70  		rsrc.ExtensionConfigType: {{Resource: testExtensionConfig}},
    71  	})
    72  
    73  	names = map[string][]string{
    74  		rsrc.EndpointType: {clusterName},
    75  		rsrc.ClusterType:  nil,
    76  		rsrc.RouteType:    {routeName},
    77  		rsrc.ListenerType: nil,
    78  		rsrc.RuntimeType:  nil,
    79  	}
    80  
    81  	testTypes = []string{
    82  		rsrc.EndpointType,
    83  		rsrc.ClusterType,
    84  		rsrc.RouteType,
    85  		rsrc.ListenerType,
    86  		rsrc.RuntimeType,
    87  	}
    88  )
    89  
    90  type logger struct {
    91  	t *testing.T
    92  }
    93  
    94  func (log logger) Debugf(format string, args ...interface{}) { log.t.Logf(format, args...) }
    95  func (log logger) Infof(format string, args ...interface{})  { log.t.Logf(format, args...) }
    96  func (log logger) Warnf(format string, args ...interface{})  { log.t.Logf(format, args...) }
    97  func (log logger) Errorf(format string, args ...interface{}) { log.t.Logf(format, args...) }
    98  
    99  func TestSnapshotCacheWithTTL(t *testing.T) {
   100  	ctx, cancel := context.WithCancel(context.Background())
   101  	defer cancel()
   102  	c := cache.NewSnapshotCacheWithHeartbeating(ctx, true, group{}, logger{t: t}, time.Second)
   103  
   104  	if _, err := c.GetSnapshot(key); err == nil {
   105  		t.Errorf("unexpected snapshot found for key %q", key)
   106  	}
   107  
   108  	if err := c.SetSnapshot(context.Background(), key, snapshotWithTTL); err != nil {
   109  		t.Fatal(err)
   110  	}
   111  
   112  	snap, err := c.GetSnapshot(key)
   113  	if err != nil {
   114  		t.Fatal(err)
   115  	}
   116  	if !reflect.DeepEqual(snap, snapshotWithTTL) {
   117  		t.Errorf("expect snapshot: %v, got: %v", snapshotWithTTL, snap)
   118  	}
   119  
   120  	wg := sync.WaitGroup{}
   121  	// All the resources should respond immediately when version is not up to date.
   122  	for _, typ := range testTypes {
   123  		wg.Add(1)
   124  		t.Run(typ, func(t *testing.T) {
   125  			defer wg.Done()
   126  			value := make(chan cache.Response, 1)
   127  			c.CreateWatch(&discovery.DiscoveryRequest{TypeUrl: typ, ResourceNames: names[typ]}, value)
   128  			select {
   129  			case out := <-value:
   130  				if gotVersion, _ := out.GetVersion(); gotVersion != version {
   131  					t.Errorf("got version %q, want %q", gotVersion, version)
   132  				}
   133  				if !reflect.DeepEqual(cache.IndexResourcesByName(out.(*cache.RawResponse).Resources), snapshotWithTTL.GetResourcesAndTTL(typ)) {
   134  					t.Errorf("get resources %v, want %v", out.(*cache.RawResponse).Resources, snapshotWithTTL.GetResourcesAndTTL(typ))
   135  				}
   136  			case <-time.After(2 * time.Second):
   137  				t.Errorf("failed to receive snapshot response")
   138  			}
   139  		})
   140  	}
   141  	wg.Wait()
   142  
   143  	// Once everything is up to date, only the TTL'd resource should send out updates.
   144  	wg = sync.WaitGroup{}
   145  	updatesByType := map[string]int{}
   146  	for _, typ := range testTypes {
   147  		wg.Add(1)
   148  		go func(typ string) {
   149  			defer wg.Done()
   150  
   151  			end := time.After(5 * time.Second)
   152  			for {
   153  				value := make(chan cache.Response, 1)
   154  				cancel := c.CreateWatch(&discovery.DiscoveryRequest{TypeUrl: typ, ResourceNames: names[typ], VersionInfo: version}, value)
   155  
   156  				select {
   157  				case out := <-value:
   158  					if gotVersion, _ := out.GetVersion(); gotVersion != version {
   159  						t.Errorf("got version %q, want %q", gotVersion, version)
   160  					}
   161  					if !reflect.DeepEqual(cache.IndexResourcesByName(out.(*cache.RawResponse).Resources), snapshotWithTTL.GetResourcesAndTTL(typ)) {
   162  						t.Errorf("get resources %v, want %v", out.(*cache.RawResponse).Resources, snapshotWithTTL.GetResources(typ))
   163  					}
   164  
   165  					if !reflect.DeepEqual(cache.IndexResourcesByName(out.(*cache.RawResponse).Resources), snapshotWithTTL.GetResourcesAndTTL(typ)) {
   166  						t.Errorf("get resources %v, want %v", out.(*cache.RawResponse).Resources, snapshotWithTTL.GetResources(typ))
   167  					}
   168  
   169  					updatesByType[typ]++
   170  				case <-end:
   171  					cancel()
   172  					return
   173  				}
   174  			}
   175  		}(typ)
   176  	}
   177  
   178  	wg.Wait()
   179  
   180  	if len(updatesByType) != 1 {
   181  		t.Errorf("expected to only receive updates for TTL'd type, got %v", updatesByType)
   182  	}
   183  	// Avoid an exact match on number of triggers to avoid this being flaky.
   184  	if updatesByType[rsrc.EndpointType] < 2 {
   185  		t.Errorf("expected at least two TTL updates for endpoints, got %d", updatesByType[rsrc.EndpointType])
   186  	}
   187  }
   188  
   189  func TestSnapshotCache(t *testing.T) {
   190  	c := cache.NewSnapshotCache(true, group{}, logger{t: t})
   191  
   192  	if _, err := c.GetSnapshot(key); err == nil {
   193  		t.Errorf("unexpected snapshot found for key %q", key)
   194  	}
   195  
   196  	if err := c.SetSnapshot(context.Background(), key, snapshot); err != nil {
   197  		t.Fatal(err)
   198  	}
   199  
   200  	snap, err := c.GetSnapshot(key)
   201  	if err != nil {
   202  		t.Fatal(err)
   203  	}
   204  	if !reflect.DeepEqual(snap, snapshot) {
   205  		t.Errorf("expect snapshot: %v, got: %v", snapshot, snap)
   206  	}
   207  
   208  	// try to get endpoints with incorrect list of names
   209  	// should not receive response
   210  	value := make(chan cache.Response, 1)
   211  	c.CreateWatch(&discovery.DiscoveryRequest{TypeUrl: rsrc.EndpointType, ResourceNames: []string{"none"}}, value)
   212  	select {
   213  	case out := <-value:
   214  		t.Errorf("watch for endpoints and mismatched names => got %v, want none", out)
   215  	case <-time.After(time.Second / 4):
   216  	}
   217  
   218  	for _, typ := range testTypes {
   219  		t.Run(typ, func(t *testing.T) {
   220  			value := make(chan cache.Response, 1)
   221  			c.CreateWatch(&discovery.DiscoveryRequest{TypeUrl: typ, ResourceNames: names[typ]}, value)
   222  			select {
   223  			case out := <-value:
   224  				if gotVersion, _ := out.GetVersion(); gotVersion != version {
   225  					t.Errorf("got version %q, want %q", gotVersion, version)
   226  				}
   227  				if !reflect.DeepEqual(cache.IndexResourcesByName(out.(*cache.RawResponse).Resources), snapshot.GetResourcesAndTTL(typ)) {
   228  					t.Errorf("get resources %v, want %v", out.(*cache.RawResponse).Resources, snapshot.GetResourcesAndTTL(typ))
   229  				}
   230  			case <-time.After(time.Second):
   231  				t.Fatal("failed to receive snapshot response")
   232  			}
   233  		})
   234  	}
   235  }
   236  
   237  func TestSnapshotCacheFetch(t *testing.T) {
   238  	c := cache.NewSnapshotCache(true, group{}, logger{t: t})
   239  	if err := c.SetSnapshot(context.Background(), key, snapshot); err != nil {
   240  		t.Fatal(err)
   241  	}
   242  
   243  	for _, typ := range testTypes {
   244  		t.Run(typ, func(t *testing.T) {
   245  			resp, err := c.Fetch(context.Background(), &discovery.DiscoveryRequest{TypeUrl: typ, ResourceNames: names[typ]})
   246  			if err != nil || resp == nil {
   247  				t.Fatal("unexpected error or null response")
   248  			}
   249  			if gotVersion, _ := resp.GetVersion(); gotVersion != version {
   250  				t.Errorf("got version %q, want %q", gotVersion, version)
   251  			}
   252  		})
   253  	}
   254  
   255  	// no response for missing snapshot
   256  	if resp, err := c.Fetch(context.Background(),
   257  		&discovery.DiscoveryRequest{TypeUrl: rsrc.ClusterType, Node: &core.Node{Id: "oof"}}); resp != nil || err == nil {
   258  		t.Errorf("missing snapshot: response is not nil %v", resp)
   259  	}
   260  
   261  	// no response for latest version
   262  	if resp, err := c.Fetch(context.Background(),
   263  		&discovery.DiscoveryRequest{TypeUrl: rsrc.ClusterType, VersionInfo: version}); resp != nil || err == nil {
   264  		t.Errorf("latest version: response is not nil %v", resp)
   265  	}
   266  }
   267  
   268  func TestSnapshotCacheWatch(t *testing.T) {
   269  	c := cache.NewSnapshotCache(true, group{}, logger{t: t})
   270  	watches := make(map[string]chan cache.Response)
   271  	for _, typ := range testTypes {
   272  		watches[typ] = make(chan cache.Response, 1)
   273  		c.CreateWatch(&discovery.DiscoveryRequest{TypeUrl: typ, ResourceNames: names[typ]}, watches[typ])
   274  	}
   275  	if err := c.SetSnapshot(context.Background(), key, snapshot); err != nil {
   276  		t.Fatal(err)
   277  	}
   278  	for _, typ := range testTypes {
   279  		t.Run(typ, func(t *testing.T) {
   280  			select {
   281  			case out := <-watches[typ]:
   282  				if gotVersion, _ := out.GetVersion(); gotVersion != version {
   283  					t.Errorf("got version %q, want %q", gotVersion, version)
   284  				}
   285  				if !reflect.DeepEqual(cache.IndexResourcesByName(out.(*cache.RawResponse).Resources), snapshot.GetResourcesAndTTL(typ)) {
   286  					t.Errorf("get resources %v, want %v", out.(*cache.RawResponse).Resources, snapshot.GetResourcesAndTTL(typ))
   287  				}
   288  			case <-time.After(time.Second):
   289  				t.Fatal("failed to receive snapshot response")
   290  			}
   291  		})
   292  	}
   293  
   294  	// open new watches with the latest version
   295  	for _, typ := range testTypes {
   296  		watches[typ] = make(chan cache.Response, 1)
   297  		c.CreateWatch(&discovery.DiscoveryRequest{TypeUrl: typ, ResourceNames: names[typ], VersionInfo: version}, watches[typ])
   298  	}
   299  	if count := c.GetStatusInfo(key).GetNumWatches(); count != len(testTypes) {
   300  		t.Errorf("watches should be created for the latest version: %d", count)
   301  	}
   302  
   303  	// set partially-versioned snapshot
   304  	snapshot2 := snapshot
   305  	snapshot2.Resources[types.Endpoint] = cache.NewResources(version2, []types.Resource{resource.MakeEndpoint(clusterName, 9090)})
   306  	if err := c.SetSnapshot(context.Background(), key, snapshot2); err != nil {
   307  		t.Fatal(err)
   308  	}
   309  	if count := c.GetStatusInfo(key).GetNumWatches(); count != len(testTypes)-1 {
   310  		t.Errorf("watches should be preserved for all but one: %d", count)
   311  	}
   312  
   313  	// validate response for endpoints
   314  	select {
   315  	case out := <-watches[rsrc.EndpointType]:
   316  		if gotVersion, _ := out.GetVersion(); gotVersion != version2 {
   317  			t.Errorf("got version %q, want %q", gotVersion, version2)
   318  		}
   319  		if !reflect.DeepEqual(cache.IndexResourcesByName(out.(*cache.RawResponse).Resources), snapshot2.Resources[types.Endpoint].Items) {
   320  			t.Errorf("got resources %v, want %v", out.(*cache.RawResponse).Resources, snapshot2.Resources[types.Endpoint].Items)
   321  		}
   322  	case <-time.After(time.Second):
   323  		t.Fatal("failed to receive snapshot response")
   324  	}
   325  }
   326  
   327  func TestConcurrentSetWatch(t *testing.T) {
   328  	c := cache.NewSnapshotCache(false, group{}, logger{t: t})
   329  	for i := 0; i < 50; i++ {
   330  		t.Run(fmt.Sprintf("worker%d", i), func(t *testing.T) {
   331  			t.Parallel()
   332  			id := fmt.Sprintf("%d", i%2)
   333  			value := make(chan cache.Response, 1)
   334  			if i < 25 {
   335  				snap := cache.Snapshot{}
   336  				snap.Resources[types.Endpoint] = cache.NewResources(fmt.Sprintf("v%d", i), []types.Resource{resource.MakeEndpoint(clusterName, uint32(i))})
   337  				if err := c.SetSnapshot(context.Background(), id, snap); err != nil {
   338  					t.Fatalf("failed to set snapshot %q: %s", id, err)
   339  				}
   340  			} else {
   341  				cancel := c.CreateWatch(&discovery.DiscoveryRequest{
   342  					Node:    &core.Node{Id: id},
   343  					TypeUrl: rsrc.EndpointType,
   344  				}, value)
   345  
   346  				defer cancel()
   347  			}
   348  		})
   349  	}
   350  }
   351  
   352  func TestSnapshotCacheWatchCancel(t *testing.T) {
   353  	c := cache.NewSnapshotCache(true, group{}, logger{t: t})
   354  	for _, typ := range testTypes {
   355  		value := make(chan cache.Response, 1)
   356  		cancel := c.CreateWatch(&discovery.DiscoveryRequest{TypeUrl: typ, ResourceNames: names[typ]}, value)
   357  		cancel()
   358  	}
   359  	// should be status info for the node
   360  	if keys := c.GetStatusKeys(); len(keys) == 0 {
   361  		t.Error("got 0, want status info for the node")
   362  	}
   363  
   364  	for _, typ := range testTypes {
   365  		if count := c.GetStatusInfo(key).GetNumWatches(); count > 0 {
   366  			t.Errorf("watches should be released for %s", typ)
   367  		}
   368  	}
   369  
   370  	if empty := c.GetStatusInfo("missing"); empty != nil {
   371  		t.Errorf("should not return a status for unknown key: got %#v", empty)
   372  	}
   373  }
   374  
   375  func TestSnapshotCacheWatchTimeout(t *testing.T) {
   376  	c := cache.NewSnapshotCache(true, group{}, logger{t: t})
   377  
   378  	// Create a non-buffered channel that will block sends.
   379  	watchCh := make(chan cache.Response)
   380  	c.CreateWatch(&discovery.DiscoveryRequest{TypeUrl: rsrc.EndpointType, ResourceNames: names[rsrc.EndpointType]}, watchCh)
   381  
   382  	// The first time we set the snapshot without consuming from the blocking channel, so this should time out.
   383  	ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond)
   384  	defer cancel()
   385  
   386  	err := c.SetSnapshot(ctx, key, snapshot)
   387  	assert.EqualError(t, err, context.Canceled.Error())
   388  
   389  	// Now reset the snapshot with a consuming channel. This verifies that if setting the snapshot fails,
   390  	// we can retry by setting the same snapshot. In other words, we keep the watch open even if we failed
   391  	// to respond to it within the deadline.
   392  	watchTriggeredCh := make(chan cache.Response)
   393  	go func() {
   394  		response := <-watchCh
   395  		watchTriggeredCh <- response
   396  		close(watchTriggeredCh)
   397  	}()
   398  
   399  	err = c.SetSnapshot(context.WithValue(context.Background(), testKey{}, "bar"), key, snapshot)
   400  	assert.NoError(t, err)
   401  
   402  	// The channel should get closed due to the watch trigger.
   403  	select {
   404  	case response := <-watchTriggeredCh:
   405  		// Verify that we pass the context through.
   406  		assert.Equal(t, response.GetContext().Value(testKey{}), "bar")
   407  	case <-time.After(time.Second):
   408  		t.Fatalf("timed out")
   409  	}
   410  }
   411  
   412  func TestSnapshotClear(t *testing.T) {
   413  	c := cache.NewSnapshotCache(true, group{}, logger{t: t})
   414  	if err := c.SetSnapshot(context.Background(), key, snapshot); err != nil {
   415  		t.Fatal(err)
   416  	}
   417  	c.ClearSnapshot(key)
   418  	if empty := c.GetStatusInfo(key); empty != nil {
   419  		t.Errorf("cache should be cleared")
   420  	}
   421  	if keys := c.GetStatusKeys(); len(keys) != 0 {
   422  		t.Errorf("keys should be empty")
   423  	}
   424  }