Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

csds: return empty response if xds client is not set #4505

Merged
merged 2 commits into from Jun 3, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 11 additions & 6 deletions xds/csds/csds.go
Expand Up @@ -25,7 +25,6 @@ package csds

import (
"context"
"fmt"
"io"
"time"

Expand Down Expand Up @@ -76,11 +75,12 @@ type ClientStatusDiscoveryServer struct {
func NewClientStatusDiscoveryServer() (*ClientStatusDiscoveryServer, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Above, s/practise/practice/.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

xdsC, err := newXDSClient()
if err != nil {
return nil, fmt.Errorf("failed to create xds client: %v", err)
logger.Warningf("failed to create xds client: %v", err)
// Return an explicit nil here, otherwise the nil returned from
// client.New() is a typed nil (type *Client, not the interface).
return &ClientStatusDiscoveryServer{}, nil
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It took me a long time to figure out what this comment means. Let's change it:

// xdsC is a typed nil here, so we if we assign it to the xdsClient field, the
// nil checks in the handlers will not handle it properly.

Or you could fix this by doing:

newXDSClient = func() (xdsClientInterface, error) {
	c, err := client.New()
	if err != nil {
		return nil, err
	}
	return c, nil
}

Or slightly better:

newXDSClient = func() xdsClientInterface {
	c, err := client.New()
	if err != nil {
		logger.Warningf(...)
		return nil
	}
	return c
}

But ACTUALLY, why do we even have newXDSClient? For testing, why not construct a ClientStatusDiscoverServer manually?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to newXDSClient = func() xdsClientInterface

But ACTUALLY, why do we even have newXDSClient? For testing, why not construct a ClientStatusDiscoverServer manually?

Why is this better? If we add more things to NewClientStatusDiscoveryServer(), they won't be covered.

}
return &ClientStatusDiscoveryServer{
xdsClient: xdsC,
}, nil
return &ClientStatusDiscoveryServer{xdsClient: xdsC}, nil
}

// StreamClientStatus implementations interface ClientStatusDiscoveryServiceServer.
Expand Down Expand Up @@ -113,6 +113,9 @@ func (s *ClientStatusDiscoveryServer) FetchClientStatus(_ context.Context, req *
//
// If it returns an error, the error is a status error.
func (s *ClientStatusDiscoveryServer) buildClientStatusRespForReq(req *v3statuspb.ClientStatusRequest) (*v3statuspb.ClientStatusResponse, error) {
if s.xdsClient == nil {
return &v3statuspb.ClientStatusResponse{}, nil
}
// Field NodeMatchers is unsupported, by design
// https://github.com/grpc/proposal/blob/master/A40-csds-support.md#detail-node-matching.
if len(req.NodeMatchers) != 0 {
Expand All @@ -137,7 +140,9 @@ func (s *ClientStatusDiscoveryServer) buildClientStatusRespForReq(req *v3statusp

// Close cleans up the resources.
func (s *ClientStatusDiscoveryServer) Close() {
s.xdsClient.Close()
if s.xdsClient != nil {
s.xdsClient.Close()
}
}

// nodeProtoToV3 converts the given proto into a v3.Node. n is from bootstrap
Expand Down
68 changes: 68 additions & 0 deletions xds/csds/csds_test.go
Expand Up @@ -37,6 +37,7 @@ import (
"google.golang.org/grpc/internal/testutils"
"google.golang.org/grpc/internal/xds"
"google.golang.org/grpc/xds/internal/client"
"google.golang.org/grpc/xds/internal/client/bootstrap"
_ "google.golang.org/grpc/xds/internal/httpfilter/router"
xtestutils "google.golang.org/grpc/xds/internal/testutils"
"google.golang.org/grpc/xds/internal/testutils/e2e"
Expand Down Expand Up @@ -635,6 +636,73 @@ func protoToJSON(p proto.Message) string {
return ret
}

type errorXDSClient struct{}

func (t errorXDSClient) DumpLDS() (string, map[string]client.UpdateWithMD) { panic("implement me") }
func (t errorXDSClient) DumpRDS() (string, map[string]client.UpdateWithMD) { panic("implement me") }
func (t errorXDSClient) DumpCDS() (string, map[string]client.UpdateWithMD) { panic("implement me") }
func (t errorXDSClient) DumpEDS() (string, map[string]client.UpdateWithMD) { panic("implement me") }
func (t errorXDSClient) BootstrapConfig() *bootstrap.Config { panic("implement me") }
func (t errorXDSClient) Close() { panic("implement me") }

func newErrorXDSClient() (*errorXDSClient, error) {
return nil, fmt.Errorf("typed nil")
}

func TestCSDSNoXDSClient(t *testing.T) {
oldNewXDSClient := newXDSClient
newXDSClient = func() (xdsClientInterface, error) {
return newErrorXDSClient()
}
defer func() { newXDSClient = oldNewXDSClient }()

// Initialize an gRPC server and register CSDS on it.
server := grpc.NewServer()
csdss, err := NewClientStatusDiscoveryServer()
if err != nil {
t.Fatal(err)
}
defer csdss.Close()
v3statuspbgrpc.RegisterClientStatusDiscoveryServiceServer(server, csdss)
// Create a local listener and pass it to Serve().
lis, err := xtestutils.LocalTCPListener()
if err != nil {
t.Fatalf("xtestutils.LocalTCPListener() failed: %v", err)
}
go func() {
if err := server.Serve(lis); err != nil {
t.Errorf("Serve() failed: %v", err)
}
}()
defer server.Stop()

// Create CSDS client.
conn, err := grpc.Dial(lis.Addr().String(), grpc.WithInsecure())
if err != nil {
t.Fatalf("cannot connect to server: %v", err)
}
defer conn.Close()
c := v3statuspbgrpc.NewClientStatusDiscoveryServiceClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
stream, err := c.StreamClientStatus(ctx, grpc.WaitForReady(true))
if err != nil {
t.Fatalf("cannot get ServerReflectionInfo: %v", err)
}

if err := stream.Send(&v3statuspb.ClientStatusRequest{Node: nil}); err != nil {
t.Fatalf("failed to send: %v", err)
}
r, err := stream.Recv()
if err != nil {
// io.EOF is not ok.
t.Fatalf("failed to recv response: %v", err)
}
if n := len(r.Config); n != 0 {
t.Fatalf("got %d configs, want 0: %v", n, proto.MarshalTextString(r))
}
}

func Test_nodeProtoToV3(t *testing.T) {
const (
testID = "test-id"
Expand Down