mirror of https://github.com/grpc/grpc-go.git
xdsclient: correct logic used to suppress empty ADS requests on new streams (#7026)
This commit is contained in:
parent
f7c5e6a762
commit
55341d7fde
|
|
@ -363,29 +363,21 @@ func (t *Transport) send(ctx context.Context) {
|
|||
// The xDS protocol only requires that we send the node proto in the first
|
||||
// discovery request on every stream. Sending the node proto in every
|
||||
// request message wastes CPU resources on the client and the server.
|
||||
sendNodeProto := true
|
||||
sentNodeProto := false
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case stream = <-t.adsStreamCh:
|
||||
// We have a new stream and we've to ensure that the node proto gets
|
||||
// sent out in the first request on the stream. At this point, we
|
||||
// might not have any registered watches. Setting this field to true
|
||||
// here will ensure that the node proto gets sent out along with the
|
||||
// discovery request when the first watch is registered.
|
||||
if len(t.resources) == 0 {
|
||||
sendNodeProto = true
|
||||
continue
|
||||
}
|
||||
|
||||
if !t.sendExisting(stream) {
|
||||
// sent out in the first request on the stream.
|
||||
var err error
|
||||
if sentNodeProto, err = t.sendExisting(stream); err != nil {
|
||||
// Send failed, clear the current stream. Attempt to resend will
|
||||
// only be made after a new stream is created.
|
||||
stream = nil
|
||||
continue
|
||||
}
|
||||
sendNodeProto = false
|
||||
case u, ok := <-t.adsRequestCh.Get():
|
||||
if !ok {
|
||||
// No requests will be sent after the adsRequestCh buffer is closed.
|
||||
|
|
@ -416,12 +408,12 @@ func (t *Transport) send(ctx context.Context) {
|
|||
// sending response back).
|
||||
continue
|
||||
}
|
||||
if err := t.sendAggregatedDiscoveryServiceRequest(stream, sendNodeProto, resources, url, version, nonce, nackErr); err != nil {
|
||||
if err := t.sendAggregatedDiscoveryServiceRequest(stream, !sentNodeProto, resources, url, version, nonce, nackErr); err != nil {
|
||||
t.logger.Warningf("Sending ADS request for resources: %q, url: %q, version: %q, nonce: %q failed: %v", resources, url, version, nonce, err)
|
||||
// Send failed, clear the current stream.
|
||||
stream = nil
|
||||
}
|
||||
sendNodeProto = false
|
||||
sentNodeProto = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -433,7 +425,9 @@ func (t *Transport) send(ctx context.Context) {
|
|||
// that here because the stream has just started and Send() usually returns
|
||||
// quickly (once it pushes the message onto the transport layer) and is only
|
||||
// ever blocked if we don't have enough flow control quota.
|
||||
func (t *Transport) sendExisting(stream adsStream) bool {
|
||||
//
|
||||
// Returns true if the node proto was sent.
|
||||
func (t *Transport) sendExisting(stream adsStream) (sentNodeProto bool, err error) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
|
|
@ -450,16 +444,18 @@ func (t *Transport) sendExisting(stream adsStream) bool {
|
|||
t.nonces = make(map[string]string)
|
||||
|
||||
// Send node proto only in the first request on the stream.
|
||||
sendNodeProto := true
|
||||
for url, resources := range t.resources {
|
||||
if err := t.sendAggregatedDiscoveryServiceRequest(stream, sendNodeProto, mapToSlice(resources), url, t.versions[url], "", nil); err != nil {
|
||||
t.logger.Warningf("Sending ADS request for resources: %q, url: %q, version: %q, nonce: %q failed: %v", resources, url, t.versions[url], "", err)
|
||||
return false
|
||||
if len(resources) == 0 {
|
||||
continue
|
||||
}
|
||||
sendNodeProto = false
|
||||
if err := t.sendAggregatedDiscoveryServiceRequest(stream, !sentNodeProto, mapToSlice(resources), url, t.versions[url], "", nil); err != nil {
|
||||
t.logger.Warningf("Sending ADS request for resources: %q, url: %q, version: %q, nonce: %q failed: %v", resources, url, t.versions[url], "", err)
|
||||
return false, err
|
||||
}
|
||||
sentNodeProto = true
|
||||
}
|
||||
|
||||
return true
|
||||
return sentNodeProto, nil
|
||||
}
|
||||
|
||||
// recv receives xDS responses on the provided ADS stream and branches out to
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ package transport_test
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
|
@ -217,3 +218,193 @@ func (s) TestHandleResponseFromManagementServer(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s) TestEmptyListenerResourceOnStreamRestart(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
||||
defer cancel()
|
||||
|
||||
mgmtServer, cleanup := startFakeManagementServer(t)
|
||||
defer cleanup()
|
||||
t.Logf("Started xDS management server on %s", mgmtServer.Address)
|
||||
nodeProto := &v3corepb.Node{Id: uuid.New().String()}
|
||||
tr, err := transport.New(transport.Options{
|
||||
ServerCfg: *xdstestutils.ServerConfigForAddress(t, mgmtServer.Address),
|
||||
OnRecvHandler: func(update transport.ResourceUpdate) error {
|
||||
return nil
|
||||
},
|
||||
OnSendHandler: func(*transport.ResourceSendInfo) {}, // No onSend handling.
|
||||
OnErrorHandler: func(error) {}, // No stream error handling.
|
||||
Backoff: func(int) time.Duration { return time.Duration(0) }, // No backoff.
|
||||
NodeProto: nodeProto,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create xDS transport: %v", err)
|
||||
}
|
||||
defer tr.Close()
|
||||
|
||||
// Send a request for a listener resource.
|
||||
const resource = "some-resource"
|
||||
tr.SendRequest(version.V3ListenerURL, []string{resource})
|
||||
|
||||
// Ensure the proper request was sent.
|
||||
val, err := mgmtServer.XDSRequestChan.Receive(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Error waiting for mgmt server response: %v", err)
|
||||
}
|
||||
wantReq := &fakeserver.Request{Req: &v3discoverypb.DiscoveryRequest{
|
||||
Node: nodeProto,
|
||||
ResourceNames: []string{resource},
|
||||
TypeUrl: "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
}}
|
||||
gotReq := val.(*fakeserver.Request)
|
||||
if diff := cmp.Diff(gotReq, wantReq, protocmp.Transform()); diff != "" {
|
||||
t.Fatalf("Discovery request received at management server is %+v, want %+v", gotReq, wantReq)
|
||||
}
|
||||
|
||||
// Remove the subscription by requesting an empty list.
|
||||
tr.SendRequest(version.V3ListenerURL, []string{})
|
||||
|
||||
// Ensure the proper request was sent.
|
||||
val, err = mgmtServer.XDSRequestChan.Receive(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Error waiting for mgmt server response: %v", err)
|
||||
}
|
||||
wantReq = &fakeserver.Request{Req: &v3discoverypb.DiscoveryRequest{
|
||||
ResourceNames: []string{},
|
||||
TypeUrl: "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
}}
|
||||
gotReq = val.(*fakeserver.Request)
|
||||
if diff := cmp.Diff(gotReq, wantReq, protocmp.Transform()); diff != "" {
|
||||
t.Fatalf("Discovery request received at management server is %+v, want %+v", gotReq, wantReq)
|
||||
}
|
||||
|
||||
// Cause the stream to restart.
|
||||
mgmtServer.XDSResponseChan <- &fakeserver.Response{Err: errors.New("go away")}
|
||||
|
||||
// Ensure no request is sent since there are no resources.
|
||||
ctxShort, cancel := context.WithTimeout(ctx, defaultTestShortTimeout)
|
||||
defer cancel()
|
||||
if got, err := mgmtServer.XDSRequestChan.Receive(ctxShort); !errors.Is(err, context.DeadlineExceeded) {
|
||||
t.Fatalf("mgmt server received request: %v; wanted DeadlineExceeded error", got)
|
||||
}
|
||||
|
||||
tr.SendRequest(version.V3ListenerURL, []string{resource})
|
||||
|
||||
// Ensure the proper request was sent with the node proto.
|
||||
val, err = mgmtServer.XDSRequestChan.Receive(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Error waiting for mgmt server response: %v", err)
|
||||
}
|
||||
wantReq = &fakeserver.Request{Req: &v3discoverypb.DiscoveryRequest{
|
||||
Node: nodeProto,
|
||||
ResourceNames: []string{resource},
|
||||
TypeUrl: "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
}}
|
||||
gotReq = val.(*fakeserver.Request)
|
||||
if diff := cmp.Diff(gotReq, wantReq, protocmp.Transform()); diff != "" {
|
||||
t.Fatalf("Discovery request received at management server is %+v, want %+v", gotReq, wantReq)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s) TestEmptyClusterResourceOnStreamRestartWithListener(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
||||
defer cancel()
|
||||
|
||||
mgmtServer, cleanup := startFakeManagementServer(t)
|
||||
defer cleanup()
|
||||
t.Logf("Started xDS management server on %s", mgmtServer.Address)
|
||||
nodeProto := &v3corepb.Node{Id: uuid.New().String()}
|
||||
tr, err := transport.New(transport.Options{
|
||||
ServerCfg: *xdstestutils.ServerConfigForAddress(t, mgmtServer.Address),
|
||||
OnRecvHandler: func(update transport.ResourceUpdate) error {
|
||||
return nil
|
||||
},
|
||||
OnSendHandler: func(*transport.ResourceSendInfo) {}, // No onSend handling.
|
||||
OnErrorHandler: func(error) {}, // No stream error handling.
|
||||
Backoff: func(int) time.Duration { return time.Duration(0) }, // No backoff.
|
||||
NodeProto: nodeProto,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create xDS transport: %v", err)
|
||||
}
|
||||
defer tr.Close()
|
||||
|
||||
// Send a request for a listener resource.
|
||||
const resource = "some-resource"
|
||||
tr.SendRequest(version.V3ListenerURL, []string{resource})
|
||||
|
||||
// Ensure the proper request was sent.
|
||||
val, err := mgmtServer.XDSRequestChan.Receive(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Error waiting for mgmt server response: %v", err)
|
||||
}
|
||||
wantReq := &fakeserver.Request{Req: &v3discoverypb.DiscoveryRequest{
|
||||
Node: nodeProto,
|
||||
ResourceNames: []string{resource},
|
||||
TypeUrl: "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
}}
|
||||
gotReq := val.(*fakeserver.Request)
|
||||
if diff := cmp.Diff(gotReq, wantReq, protocmp.Transform()); diff != "" {
|
||||
t.Fatalf("Discovery request received at management server is %+v, want %+v", gotReq, wantReq)
|
||||
}
|
||||
|
||||
// Send a request for a cluster resource.
|
||||
tr.SendRequest(version.V3ClusterURL, []string{resource})
|
||||
|
||||
// Ensure the proper request was sent.
|
||||
val, err = mgmtServer.XDSRequestChan.Receive(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Error waiting for mgmt server response: %v", err)
|
||||
}
|
||||
wantReq = &fakeserver.Request{Req: &v3discoverypb.DiscoveryRequest{
|
||||
ResourceNames: []string{resource},
|
||||
TypeUrl: "type.googleapis.com/envoy.config.cluster.v3.Cluster",
|
||||
}}
|
||||
gotReq = val.(*fakeserver.Request)
|
||||
if diff := cmp.Diff(gotReq, wantReq, protocmp.Transform()); diff != "" {
|
||||
t.Fatalf("Discovery request received at management server is %+v, want %+v", gotReq, wantReq)
|
||||
}
|
||||
|
||||
// Remove the cluster subscription by requesting an empty list.
|
||||
tr.SendRequest(version.V3ClusterURL, []string{})
|
||||
|
||||
// Ensure the proper request was sent.
|
||||
val, err = mgmtServer.XDSRequestChan.Receive(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Error waiting for mgmt server response: %v", err)
|
||||
}
|
||||
wantReq = &fakeserver.Request{Req: &v3discoverypb.DiscoveryRequest{
|
||||
ResourceNames: []string{},
|
||||
TypeUrl: "type.googleapis.com/envoy.config.cluster.v3.Cluster",
|
||||
}}
|
||||
gotReq = val.(*fakeserver.Request)
|
||||
if diff := cmp.Diff(gotReq, wantReq, protocmp.Transform()); diff != "" {
|
||||
t.Fatalf("Discovery request received at management server is %+v, want %+v", gotReq, wantReq)
|
||||
}
|
||||
|
||||
// Cause the stream to restart.
|
||||
mgmtServer.XDSResponseChan <- &fakeserver.Response{Err: errors.New("go away")}
|
||||
|
||||
// Ensure the proper LDS request was sent.
|
||||
val, err = mgmtServer.XDSRequestChan.Receive(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Error waiting for mgmt server response: %v", err)
|
||||
}
|
||||
wantReq = &fakeserver.Request{Req: &v3discoverypb.DiscoveryRequest{
|
||||
Node: nodeProto,
|
||||
ResourceNames: []string{resource},
|
||||
TypeUrl: "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||
}}
|
||||
gotReq = val.(*fakeserver.Request)
|
||||
if diff := cmp.Diff(gotReq, wantReq, protocmp.Transform()); diff != "" {
|
||||
t.Fatalf("Discovery request received at management server is %+v, want %+v", gotReq, wantReq)
|
||||
}
|
||||
|
||||
// Ensure no cluster request is sent since there are no cluster resources.
|
||||
ctxShort, cancel := context.WithTimeout(ctx, defaultTestShortTimeout)
|
||||
defer cancel()
|
||||
if got, err := mgmtServer.XDSRequestChan.Receive(ctxShort); !errors.Is(err, context.DeadlineExceeded) {
|
||||
t.Fatalf("mgmt server received request: %v; wanted DeadlineExceeded error", got)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue