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