gitee.com/ks-custle/core-gm@v0.0.0-20230922171213-b83bdd97b62c/go-control-plane/pkg/server/v3/server_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 server_test
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"reflect"
    22  	"sync"
    23  	"testing"
    24  	"time"
    25  
    26  	"gitee.com/ks-custle/core-gm/grpc"
    27  
    28  	core "gitee.com/ks-custle/core-gm/go-control-plane/envoy/config/core/v3"
    29  	discovery "gitee.com/ks-custle/core-gm/go-control-plane/envoy/service/discovery/v3"
    30  	"gitee.com/ks-custle/core-gm/go-control-plane/pkg/cache/types"
    31  	"gitee.com/ks-custle/core-gm/go-control-plane/pkg/cache/v3"
    32  	rsrc "gitee.com/ks-custle/core-gm/go-control-plane/pkg/resource/v3"
    33  	"gitee.com/ks-custle/core-gm/go-control-plane/pkg/server/v3"
    34  	"gitee.com/ks-custle/core-gm/go-control-plane/pkg/test/resource/v3"
    35  )
    36  
    37  type mockConfigWatcher struct {
    38  	counts         map[string]int
    39  	deltaCounts    map[string]int
    40  	responses      map[string][]cache.Response
    41  	deltaResponses map[string][]cache.DeltaResponse
    42  	watches        int
    43  	deltaWatches   int
    44  
    45  	mu *sync.RWMutex
    46  }
    47  
    48  func (config *mockConfigWatcher) CreateWatch(req *discovery.DiscoveryRequest, out chan cache.Response) func() {
    49  	config.counts[req.TypeUrl] = config.counts[req.TypeUrl] + 1
    50  	if len(config.responses[req.TypeUrl]) > 0 {
    51  		out <- config.responses[req.TypeUrl][0]
    52  		config.responses[req.TypeUrl] = config.responses[req.TypeUrl][1:]
    53  	} else {
    54  		config.watches++
    55  		return func() {
    56  			config.watches--
    57  		}
    58  	}
    59  	return nil
    60  }
    61  
    62  //goland:noinspection GoUnusedParameter
    63  func (config *mockConfigWatcher) Fetch(ctx context.Context, req *discovery.DiscoveryRequest) (cache.Response, error) {
    64  	if len(config.responses[req.TypeUrl]) > 0 {
    65  		out := config.responses[req.TypeUrl][0]
    66  		config.responses[req.TypeUrl] = config.responses[req.TypeUrl][1:]
    67  		return out, nil
    68  	}
    69  	return nil, errors.New("missing")
    70  }
    71  
    72  func makeMockConfigWatcher() *mockConfigWatcher {
    73  	return &mockConfigWatcher{
    74  		counts:      make(map[string]int),
    75  		deltaCounts: make(map[string]int),
    76  		mu:          &sync.RWMutex{},
    77  	}
    78  }
    79  
    80  type mockStream struct {
    81  	t         *testing.T
    82  	ctx       context.Context
    83  	recv      chan *discovery.DiscoveryRequest
    84  	sent      chan *discovery.DiscoveryResponse
    85  	nonce     int
    86  	sendError bool
    87  	grpc.ServerStream
    88  }
    89  
    90  func (stream *mockStream) Context() context.Context {
    91  	return stream.ctx
    92  }
    93  
    94  func (stream *mockStream) Send(resp *discovery.DiscoveryResponse) error {
    95  	// check that nonce is monotonically incrementing
    96  	stream.nonce = stream.nonce + 1
    97  	if resp.Nonce != fmt.Sprintf("%d", stream.nonce) {
    98  		stream.t.Errorf("Nonce => got %q, want %d", resp.Nonce, stream.nonce)
    99  	}
   100  	// check that version is set
   101  	if resp.VersionInfo == "" {
   102  		stream.t.Error("VersionInfo => got none, want non-empty")
   103  	}
   104  	// check resources are non-empty
   105  	if len(resp.Resources) == 0 {
   106  		stream.t.Error("Resources => got none, want non-empty")
   107  	}
   108  	// check that type URL matches in resources
   109  	if resp.TypeUrl == "" {
   110  		stream.t.Error("TypeUrl => got none, want non-empty")
   111  	}
   112  	for _, res := range resp.Resources {
   113  		if res.TypeUrl != resp.TypeUrl {
   114  			stream.t.Errorf("TypeUrl => got %q, want %q", res.TypeUrl, resp.TypeUrl)
   115  		}
   116  	}
   117  	stream.sent <- resp
   118  	if stream.sendError {
   119  		return errors.New("send error")
   120  	}
   121  	return nil
   122  }
   123  
   124  func (stream *mockStream) Recv() (*discovery.DiscoveryRequest, error) {
   125  	req, more := <-stream.recv
   126  	if !more {
   127  		return nil, errors.New("empty")
   128  	}
   129  	return req, nil
   130  }
   131  
   132  func makeMockStream(t *testing.T) *mockStream {
   133  	return &mockStream{
   134  		t:    t,
   135  		ctx:  context.Background(),
   136  		sent: make(chan *discovery.DiscoveryResponse, 10),
   137  		recv: make(chan *discovery.DiscoveryRequest, 10),
   138  	}
   139  }
   140  
   141  const (
   142  	clusterName         = "cluster0"
   143  	routeName           = "route0"
   144  	listenerName        = "listener0"
   145  	secretName          = "secret0"
   146  	runtimeName         = "runtime0"
   147  	extensionConfigName = "extensionConfig0"
   148  )
   149  
   150  var (
   151  	node = &core.Node{
   152  		Id:      "test-id",
   153  		Cluster: "test-cluster",
   154  	}
   155  	endpoint        = resource.MakeEndpoint(clusterName, 8080)
   156  	cluster         = resource.MakeCluster(resource.Ads, clusterName)
   157  	route           = resource.MakeRoute(routeName, clusterName)
   158  	listener        = resource.MakeHTTPListener(resource.Ads, listenerName, 80, routeName)
   159  	secret          = resource.MakeSecrets(secretName, "test")[0]
   160  	runtime         = resource.MakeRuntime(runtimeName)
   161  	extensionConfig = resource.MakeExtensionConfig(resource.Ads, extensionConfigName, routeName)
   162  	opaque          = &core.Address{}
   163  	opaqueType      = "unknown-type"
   164  	testTypes       = []string{
   165  		rsrc.EndpointType,
   166  		rsrc.ClusterType,
   167  		rsrc.RouteType,
   168  		rsrc.ListenerType,
   169  		rsrc.SecretType,
   170  		rsrc.RuntimeType,
   171  		rsrc.ExtensionConfigType,
   172  		opaqueType,
   173  	}
   174  )
   175  
   176  func makeResponses() map[string][]cache.Response {
   177  	return map[string][]cache.Response{
   178  		rsrc.EndpointType: {
   179  			&cache.RawResponse{
   180  				Version:   "1",
   181  				Resources: []types.ResourceWithTTL{{Resource: endpoint}},
   182  				Request:   &discovery.DiscoveryRequest{TypeUrl: rsrc.EndpointType},
   183  			},
   184  		},
   185  		rsrc.ClusterType: {
   186  			&cache.RawResponse{
   187  				Version:   "2",
   188  				Resources: []types.ResourceWithTTL{{Resource: cluster}},
   189  				Request:   &discovery.DiscoveryRequest{TypeUrl: rsrc.ClusterType},
   190  			},
   191  		},
   192  		rsrc.RouteType: {
   193  			&cache.RawResponse{
   194  				Version:   "3",
   195  				Resources: []types.ResourceWithTTL{{Resource: route}},
   196  				Request:   &discovery.DiscoveryRequest{TypeUrl: rsrc.RouteType},
   197  			},
   198  		},
   199  		rsrc.ListenerType: {
   200  			&cache.RawResponse{
   201  				Version:   "4",
   202  				Resources: []types.ResourceWithTTL{{Resource: listener}},
   203  				Request:   &discovery.DiscoveryRequest{TypeUrl: rsrc.ListenerType},
   204  			},
   205  		},
   206  		rsrc.SecretType: {
   207  			&cache.RawResponse{
   208  				Version:   "5",
   209  				Resources: []types.ResourceWithTTL{{Resource: secret}},
   210  				Request:   &discovery.DiscoveryRequest{TypeUrl: rsrc.SecretType},
   211  			},
   212  		},
   213  		rsrc.RuntimeType: {
   214  			&cache.RawResponse{
   215  				Version:   "6",
   216  				Resources: []types.ResourceWithTTL{{Resource: runtime}},
   217  				Request:   &discovery.DiscoveryRequest{TypeUrl: rsrc.RuntimeType},
   218  			},
   219  		},
   220  		rsrc.ExtensionConfigType: {
   221  			&cache.RawResponse{
   222  				Version:   "7",
   223  				Resources: []types.ResourceWithTTL{{Resource: extensionConfig}},
   224  				Request:   &discovery.DiscoveryRequest{TypeUrl: rsrc.ExtensionConfigType},
   225  			},
   226  		},
   227  		// Pass-through type (xDS does not exist for this type)
   228  		opaqueType: {
   229  			&cache.RawResponse{
   230  				Version:   "8",
   231  				Resources: []types.ResourceWithTTL{{Resource: opaque}},
   232  				Request:   &discovery.DiscoveryRequest{TypeUrl: opaqueType},
   233  			},
   234  		},
   235  	}
   236  }
   237  
   238  func TestServerShutdown(t *testing.T) {
   239  	for _, typ := range testTypes {
   240  		t.Run(typ, func(t *testing.T) {
   241  			config := makeMockConfigWatcher()
   242  			config.responses = makeResponses()
   243  			shutdown := make(chan bool)
   244  			ctx, cancel := context.WithCancel(context.Background())
   245  			s := server.NewServer(ctx, config, server.CallbackFuncs{})
   246  
   247  			// make a request
   248  			resp := makeMockStream(t)
   249  			resp.recv <- &discovery.DiscoveryRequest{Node: node, TypeUrl: typ}
   250  			go func(rType string) {
   251  				var err error
   252  				switch rType {
   253  				case rsrc.EndpointType:
   254  					err = s.StreamEndpoints(resp)
   255  				case rsrc.ClusterType:
   256  					err = s.StreamClusters(resp)
   257  				case rsrc.RouteType:
   258  					err = s.StreamRoutes(resp)
   259  				case rsrc.ListenerType:
   260  					err = s.StreamListeners(resp)
   261  				case rsrc.SecretType:
   262  					err = s.StreamSecrets(resp)
   263  				case rsrc.RuntimeType:
   264  					err = s.StreamRuntime(resp)
   265  				case rsrc.ExtensionConfigType:
   266  					err = s.StreamExtensionConfigs(resp)
   267  				case opaqueType:
   268  					err = s.StreamAggregatedResources(resp)
   269  				}
   270  				if err != nil {
   271  					t.Errorf("Stream() => got %v, want no error", err)
   272  				}
   273  				shutdown <- true
   274  			}(typ)
   275  
   276  			go func() {
   277  				defer cancel()
   278  			}()
   279  
   280  			select {
   281  			case <-shutdown:
   282  			case <-time.After(1 * time.Second):
   283  				t.Fatalf("got no response")
   284  			}
   285  		})
   286  	}
   287  }
   288  
   289  func TestResponseHandlers(t *testing.T) {
   290  	for _, typ := range testTypes {
   291  		t.Run(typ, func(t *testing.T) {
   292  			config := makeMockConfigWatcher()
   293  			config.responses = makeResponses()
   294  			s := server.NewServer(context.Background(), config, server.CallbackFuncs{})
   295  
   296  			// make a request
   297  			resp := makeMockStream(t)
   298  			resp.recv <- &discovery.DiscoveryRequest{Node: node, TypeUrl: typ}
   299  			go func(rType string) {
   300  				var err error
   301  				switch rType {
   302  				case rsrc.EndpointType:
   303  					err = s.StreamEndpoints(resp)
   304  				case rsrc.ClusterType:
   305  					err = s.StreamClusters(resp)
   306  				case rsrc.RouteType:
   307  					err = s.StreamRoutes(resp)
   308  				case rsrc.ListenerType:
   309  					err = s.StreamListeners(resp)
   310  				case rsrc.SecretType:
   311  					err = s.StreamSecrets(resp)
   312  				case rsrc.RuntimeType:
   313  					err = s.StreamRuntime(resp)
   314  				case rsrc.ExtensionConfigType:
   315  					err = s.StreamExtensionConfigs(resp)
   316  				case opaqueType:
   317  					err = s.StreamAggregatedResources(resp)
   318  				}
   319  				if err != nil {
   320  					t.Errorf("Stream() => got %v, want no error", err)
   321  				}
   322  			}(typ)
   323  
   324  			// check a response
   325  			select {
   326  			case <-resp.sent:
   327  				close(resp.recv)
   328  				if want := map[string]int{typ: 1}; !reflect.DeepEqual(want, config.counts) {
   329  					t.Errorf("watch counts => got %v, want %v", config.counts, want)
   330  				}
   331  			case <-time.After(1 * time.Second):
   332  				t.Fatalf("got no response")
   333  			}
   334  		})
   335  	}
   336  }
   337  
   338  func TestFetch(t *testing.T) {
   339  	config := makeMockConfigWatcher()
   340  	config.responses = makeResponses()
   341  
   342  	requestCount := 0
   343  	responseCount := 0
   344  	callbackError := false
   345  
   346  	cb := server.CallbackFuncs{
   347  		StreamOpenFunc: func(ctx context.Context, i int64, s string) error {
   348  			if callbackError {
   349  				return errors.New("stream open error")
   350  			}
   351  			return nil
   352  		},
   353  		FetchRequestFunc: func(ctx context.Context, request *discovery.DiscoveryRequest) error {
   354  			if callbackError {
   355  				return errors.New("fetch request error")
   356  			}
   357  			requestCount++
   358  			return nil
   359  		},
   360  		FetchResponseFunc: func(request *discovery.DiscoveryRequest, response *discovery.DiscoveryResponse) {
   361  			responseCount++
   362  		},
   363  	}
   364  
   365  	s := server.NewServer(context.Background(), config, cb)
   366  	if out, err := s.FetchEndpoints(context.Background(), &discovery.DiscoveryRequest{Node: node}); out == nil || err != nil {
   367  		t.Errorf("unexpected empty or error for endpoints: %v", err)
   368  	}
   369  	if out, err := s.FetchClusters(context.Background(), &discovery.DiscoveryRequest{Node: node}); out == nil || err != nil {
   370  		t.Errorf("unexpected empty or error for clusters: %v", err)
   371  	}
   372  	if out, err := s.FetchRoutes(context.Background(), &discovery.DiscoveryRequest{Node: node}); out == nil || err != nil {
   373  		t.Errorf("unexpected empty or error for routes: %v", err)
   374  	}
   375  	if out, err := s.FetchListeners(context.Background(), &discovery.DiscoveryRequest{Node: node}); out == nil || err != nil {
   376  		t.Errorf("unexpected empty or error for listeners: %v", err)
   377  	}
   378  	if out, err := s.FetchSecrets(context.Background(), &discovery.DiscoveryRequest{Node: node}); out == nil || err != nil {
   379  		t.Errorf("unexpected empty or error for secrets: %v", err)
   380  	}
   381  	if out, err := s.FetchRuntime(context.Background(), &discovery.DiscoveryRequest{Node: node}); out == nil || err != nil {
   382  		t.Errorf("unexpected empty or error for runtime: %v", err)
   383  	}
   384  	if out, err := s.FetchExtensionConfigs(context.Background(), &discovery.DiscoveryRequest{Node: node}); out == nil || err != nil {
   385  		t.Errorf("unexpected empty or error for extensionConfigs: %v", err)
   386  	}
   387  
   388  	// try again and expect empty results
   389  	if out, err := s.FetchEndpoints(context.Background(), &discovery.DiscoveryRequest{Node: node}); out != nil {
   390  		t.Errorf("expected empty or error for endpoints: %v", err)
   391  	}
   392  	if out, err := s.FetchClusters(context.Background(), &discovery.DiscoveryRequest{Node: node}); out != nil {
   393  		t.Errorf("expected empty or error for clusters: %v", err)
   394  	}
   395  	if out, err := s.FetchRoutes(context.Background(), &discovery.DiscoveryRequest{Node: node}); out != nil {
   396  		t.Errorf("expected empty or error for routes: %v", err)
   397  	}
   398  	if out, err := s.FetchListeners(context.Background(), &discovery.DiscoveryRequest{Node: node}); out != nil {
   399  		t.Errorf("expected empty or error for listeners: %v", err)
   400  	}
   401  
   402  	// try empty requests: not valid in a real gRPC server
   403  	if out, err := s.FetchEndpoints(context.Background(), nil); out != nil {
   404  		t.Errorf("expected empty on empty request: %v", err)
   405  	}
   406  	if out, err := s.FetchClusters(context.Background(), nil); out != nil {
   407  		t.Errorf("expected empty on empty request: %v", err)
   408  	}
   409  	if out, err := s.FetchRoutes(context.Background(), nil); out != nil {
   410  		t.Errorf("expected empty on empty request: %v", err)
   411  	}
   412  	if out, err := s.FetchListeners(context.Background(), nil); out != nil {
   413  		t.Errorf("expected empty on empty request: %v", err)
   414  	}
   415  	if out, err := s.FetchSecrets(context.Background(), nil); out != nil {
   416  		t.Errorf("expected empty on empty request: %v", err)
   417  	}
   418  	if out, err := s.FetchRuntime(context.Background(), nil); out != nil {
   419  		t.Errorf("expected empty on empty request: %v", err)
   420  	}
   421  	if out, err := s.FetchExtensionConfigs(context.Background(), nil); out != nil {
   422  		t.Errorf("expected empty on empty request: %v", err)
   423  	}
   424  
   425  	// send error from callback
   426  	callbackError = true
   427  	if out, err := s.FetchEndpoints(context.Background(), &discovery.DiscoveryRequest{Node: node}); out != nil || err == nil {
   428  		t.Errorf("expected empty or error due to callback error")
   429  	}
   430  	if out, err := s.FetchClusters(context.Background(), &discovery.DiscoveryRequest{Node: node}); out != nil || err == nil {
   431  		t.Errorf("expected empty or error due to callback error")
   432  	}
   433  	if out, err := s.FetchRoutes(context.Background(), &discovery.DiscoveryRequest{Node: node}); out != nil || err == nil {
   434  		t.Errorf("expected empty or error due to callback error")
   435  	}
   436  	if out, err := s.FetchListeners(context.Background(), &discovery.DiscoveryRequest{Node: node}); out != nil || err == nil {
   437  		t.Errorf("expected empty or error due to callback error")
   438  	}
   439  
   440  	// verify fetch callbacks
   441  	if want := 11; requestCount != want {
   442  		t.Errorf("unexpected number of fetch requests: got %d, want %d", requestCount, want)
   443  	}
   444  	if want := 7; responseCount != want {
   445  		t.Errorf("unexpected number of fetch responses: got %d, want %d", responseCount, want)
   446  	}
   447  }
   448  
   449  func TestSendError(t *testing.T) {
   450  	for _, typ := range testTypes {
   451  		t.Run(typ, func(t *testing.T) {
   452  			config := makeMockConfigWatcher()
   453  			config.responses = makeResponses()
   454  			s := server.NewServer(context.Background(), config, server.CallbackFuncs{})
   455  
   456  			// make a request
   457  			resp := makeMockStream(t)
   458  			resp.sendError = true
   459  			resp.recv <- &discovery.DiscoveryRequest{
   460  				Node:    node,
   461  				TypeUrl: typ,
   462  			}
   463  
   464  			// check that response fails since send returns error
   465  			if err := s.StreamAggregatedResources(resp); err == nil {
   466  				t.Error("Stream() => got no error, want send error")
   467  			}
   468  
   469  			close(resp.recv)
   470  		})
   471  	}
   472  }
   473  
   474  func TestStaleNonce(t *testing.T) {
   475  	for _, typ := range testTypes {
   476  		t.Run(typ, func(t *testing.T) {
   477  			config := makeMockConfigWatcher()
   478  			config.responses = makeResponses()
   479  			s := server.NewServer(context.Background(), config, server.CallbackFuncs{})
   480  
   481  			resp := makeMockStream(t)
   482  			resp.recv <- &discovery.DiscoveryRequest{
   483  				Node:    node,
   484  				TypeUrl: typ,
   485  			}
   486  			stop := make(chan struct{})
   487  			go func() {
   488  				if err := s.StreamAggregatedResources(resp); err != nil {
   489  					t.Errorf("StreamAggregatedResources() => got %v, want no error", err)
   490  				}
   491  				// should be two watches called
   492  				if want := map[string]int{typ: 2}; !reflect.DeepEqual(want, config.counts) {
   493  					t.Errorf("watch counts => got %v, want %v", config.counts, want)
   494  				}
   495  				close(stop)
   496  			}()
   497  			select {
   498  			case <-resp.sent:
   499  				// stale request
   500  				resp.recv <- &discovery.DiscoveryRequest{
   501  					Node:          node,
   502  					TypeUrl:       typ,
   503  					ResponseNonce: "xyz",
   504  				}
   505  				// fresh request
   506  				resp.recv <- &discovery.DiscoveryRequest{
   507  					VersionInfo:   "1",
   508  					Node:          node,
   509  					TypeUrl:       typ,
   510  					ResponseNonce: "1",
   511  				}
   512  				close(resp.recv)
   513  			case <-time.After(1 * time.Second):
   514  				t.Fatalf("got %d messages on the stream, not 4", resp.nonce)
   515  			}
   516  			<-stop
   517  		})
   518  	}
   519  }
   520  
   521  func TestAggregatedHandlers(t *testing.T) {
   522  	config := makeMockConfigWatcher()
   523  	config.responses = makeResponses()
   524  	resp := makeMockStream(t)
   525  
   526  	resp.recv <- &discovery.DiscoveryRequest{
   527  		Node:    node,
   528  		TypeUrl: rsrc.ListenerType,
   529  	}
   530  	// Delta compress node
   531  	resp.recv <- &discovery.DiscoveryRequest{
   532  		TypeUrl: rsrc.ClusterType,
   533  	}
   534  	resp.recv <- &discovery.DiscoveryRequest{
   535  		TypeUrl:       rsrc.EndpointType,
   536  		ResourceNames: []string{clusterName},
   537  	}
   538  	resp.recv <- &discovery.DiscoveryRequest{
   539  		TypeUrl:       rsrc.RouteType,
   540  		ResourceNames: []string{routeName},
   541  	}
   542  
   543  	s := server.NewServer(context.Background(), config, server.CallbackFuncs{})
   544  	go func() {
   545  		if err := s.StreamAggregatedResources(resp); err != nil {
   546  			t.Errorf("StreamAggregatedResources() => got %v, want no error", err)
   547  		}
   548  	}()
   549  
   550  	count := 0
   551  	for {
   552  		select {
   553  		case <-resp.sent:
   554  			count++
   555  			if count >= 4 {
   556  				close(resp.recv)
   557  				if want := map[string]int{
   558  					rsrc.EndpointType: 1,
   559  					rsrc.ClusterType:  1,
   560  					rsrc.RouteType:    1,
   561  					rsrc.ListenerType: 1,
   562  				}; !reflect.DeepEqual(want, config.counts) {
   563  					t.Errorf("watch counts => got %v, want %v", config.counts, want)
   564  				}
   565  
   566  				// got all messages
   567  				return
   568  			}
   569  		case <-time.After(1 * time.Second):
   570  			t.Fatalf("got %d messages on the stream, not 4", count)
   571  		}
   572  	}
   573  }
   574  
   575  func TestAggregateRequestType(t *testing.T) {
   576  	config := makeMockConfigWatcher()
   577  	s := server.NewServer(context.Background(), config, server.CallbackFuncs{})
   578  	resp := makeMockStream(t)
   579  	resp.recv <- &discovery.DiscoveryRequest{Node: node}
   580  	if err := s.StreamAggregatedResources(resp); err == nil {
   581  		t.Error("StreamAggregatedResources() => got nil, want an error")
   582  	}
   583  }
   584  
   585  func TestCancellations(t *testing.T) {
   586  	config := makeMockConfigWatcher()
   587  	resp := makeMockStream(t)
   588  	for _, typ := range testTypes {
   589  		resp.recv <- &discovery.DiscoveryRequest{
   590  			Node:    node,
   591  			TypeUrl: typ,
   592  		}
   593  	}
   594  	close(resp.recv)
   595  	s := server.NewServer(context.Background(), config, server.CallbackFuncs{})
   596  	if err := s.StreamAggregatedResources(resp); err != nil {
   597  		t.Errorf("StreamAggregatedResources() => got %v, want no error", err)
   598  	}
   599  	if config.watches != 0 {
   600  		t.Errorf("Expect all watches canceled, got %q", config.watches)
   601  	}
   602  }
   603  
   604  func TestOpaqueRequestsChannelMuxing(t *testing.T) {
   605  	config := makeMockConfigWatcher()
   606  	resp := makeMockStream(t)
   607  	for i := 0; i < 10; i++ {
   608  		resp.recv <- &discovery.DiscoveryRequest{
   609  			Node:    node,
   610  			TypeUrl: fmt.Sprintf("%s%d", opaqueType, i%2),
   611  			// each subsequent request is assumed to supercede the previous request
   612  			ResourceNames: []string{fmt.Sprintf("%d", i)},
   613  		}
   614  	}
   615  	close(resp.recv)
   616  	s := server.NewServer(context.Background(), config, server.CallbackFuncs{})
   617  	if err := s.StreamAggregatedResources(resp); err != nil {
   618  		t.Errorf("StreamAggregatedResources() => got %v, want no error", err)
   619  	}
   620  	if config.watches != 0 {
   621  		t.Errorf("Expect all watches canceled, got %q", config.watches)
   622  	}
   623  }
   624  
   625  func TestCallbackError(t *testing.T) {
   626  	for _, typ := range testTypes {
   627  		t.Run(typ, func(t *testing.T) {
   628  			config := makeMockConfigWatcher()
   629  			config.responses = makeResponses()
   630  
   631  			s := server.NewServer(context.Background(), config, server.CallbackFuncs{
   632  				StreamOpenFunc: func(ctx context.Context, i int64, s string) error {
   633  					return errors.New("stream open error")
   634  				},
   635  			})
   636  
   637  			// make a request
   638  			resp := makeMockStream(t)
   639  			resp.recv <- &discovery.DiscoveryRequest{
   640  				Node:    node,
   641  				TypeUrl: typ,
   642  			}
   643  
   644  			// check that response fails since stream open returns error
   645  			if err := s.StreamAggregatedResources(resp); err == nil {
   646  				t.Error("Stream() => got no error, want error")
   647  			}
   648  
   649  			close(resp.recv)
   650  		})
   651  	}
   652  }