google.golang.org/grpc@v1.62.1/xds/internal/xdsclient/transport/loadreport_test.go (about) 1 /* 2 * 3 * Copyright 2022 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package transport_test 19 20 import ( 21 "context" 22 "testing" 23 "time" 24 25 v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" 26 "github.com/google/go-cmp/cmp" 27 "github.com/google/uuid" 28 "google.golang.org/grpc/internal/testutils/xds/fakeserver" 29 "google.golang.org/grpc/xds/internal/testutils" 30 "google.golang.org/grpc/xds/internal/xdsclient/transport" 31 "google.golang.org/protobuf/testing/protocmp" 32 "google.golang.org/protobuf/types/known/durationpb" 33 34 v3endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3" 35 v3lrspb "github.com/envoyproxy/go-control-plane/envoy/service/load_stats/v3" 36 ) 37 38 func (s) TestReportLoad(t *testing.T) { 39 // Create a fake xDS management server listening on a local port. 40 mgmtServer, cleanup := startFakeManagementServer(t) 41 defer cleanup() 42 t.Logf("Started xDS management server on %s", mgmtServer.Address) 43 44 // Create a transport to the fake management server. 45 nodeProto := &v3corepb.Node{Id: uuid.New().String()} 46 tr, err := transport.New(transport.Options{ 47 ServerCfg: *testutils.ServerConfigForAddress(t, mgmtServer.Address), 48 NodeProto: nodeProto, 49 OnRecvHandler: func(transport.ResourceUpdate) error { return nil }, // No ADS validation. 50 OnErrorHandler: func(error) {}, // No ADS stream error handling. 51 OnSendHandler: func(*transport.ResourceSendInfo) {}, // No ADS stream update handling. 52 Backoff: func(int) time.Duration { return time.Duration(0) }, // No backoff. 53 }) 54 if err != nil { 55 t.Fatalf("Failed to create xDS transport: %v", err) 56 } 57 defer tr.Close() 58 59 // Ensure that a new connection is made to the management server. 60 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 61 defer cancel() 62 if _, err := mgmtServer.NewConnChan.Receive(ctx); err != nil { 63 t.Fatalf("Timeout when waiting for a new connection to the management server: %v", err) 64 } 65 66 // Call the load reporting API, and ensure that an LRS stream is created. 67 store1, cancelLRS1 := tr.ReportLoad() 68 if err != nil { 69 t.Fatalf("Failed to start LRS load reporting: %v", err) 70 } 71 if _, err := mgmtServer.LRSStreamOpenChan.Receive(ctx); err != nil { 72 t.Fatalf("Timeout when waiting for LRS stream to be created: %v", err) 73 } 74 75 // Push some loads on the received store. 76 store1.PerCluster("cluster1", "eds1").CallDropped("test") 77 78 // Ensure the initial request is received. 79 req, err := mgmtServer.LRSRequestChan.Receive(ctx) 80 if err != nil { 81 t.Fatalf("Timeout when waiting for initial LRS request: %v", err) 82 } 83 gotInitialReq := req.(*fakeserver.Request).Req.(*v3lrspb.LoadStatsRequest) 84 nodeProto.ClientFeatures = []string{"envoy.lrs.supports_send_all_clusters"} 85 wantInitialReq := &v3lrspb.LoadStatsRequest{Node: nodeProto} 86 if diff := cmp.Diff(gotInitialReq, wantInitialReq, protocmp.Transform()); diff != "" { 87 t.Fatalf("Unexpected diff in initial LRS request (-got, +want):\n%s", diff) 88 } 89 90 // Send a response from the server with a small deadline. 91 mgmtServer.LRSResponseChan <- &fakeserver.Response{ 92 Resp: &v3lrspb.LoadStatsResponse{ 93 SendAllClusters: true, 94 LoadReportingInterval: &durationpb.Duration{Nanos: 50000000}, // 50ms 95 }, 96 } 97 98 // Ensure that loads are seen on the server. 99 req, err = mgmtServer.LRSRequestChan.Receive(ctx) 100 if err != nil { 101 t.Fatalf("Timeout when waiting for LRS request with loads: %v", err) 102 } 103 gotLoad := req.(*fakeserver.Request).Req.(*v3lrspb.LoadStatsRequest).ClusterStats 104 if l := len(gotLoad); l != 1 { 105 t.Fatalf("Received load for %d clusters, want 1", l) 106 } 107 // This field is set by the client to indicate the actual time elapsed since 108 // the last report was sent. We cannot deterministically compare this, and 109 // we cannot use the cmpopts.IgnoreFields() option on proto structs, since 110 // we already use the protocmp.Transform() which marshals the struct into 111 // another message. Hence setting this field to nil is the best option here. 112 gotLoad[0].LoadReportInterval = nil 113 wantLoad := &v3endpointpb.ClusterStats{ 114 ClusterName: "cluster1", 115 ClusterServiceName: "eds1", 116 TotalDroppedRequests: 1, 117 DroppedRequests: []*v3endpointpb.ClusterStats_DroppedRequests{{Category: "test", DroppedCount: 1}}, 118 } 119 if diff := cmp.Diff(wantLoad, gotLoad[0], protocmp.Transform()); diff != "" { 120 t.Fatalf("Unexpected diff in LRS request (-got, +want):\n%s", diff) 121 } 122 123 // Make another call to the load reporting API, and ensure that a new LRS 124 // stream is not created. 125 store2, cancelLRS2 := tr.ReportLoad() 126 if err != nil { 127 t.Fatalf("Failed to start LRS load reporting: %v", err) 128 } 129 sCtx, sCancel := context.WithTimeout(context.Background(), defaultTestShortTimeout) 130 defer sCancel() 131 if _, err := mgmtServer.LRSStreamOpenChan.Receive(sCtx); err != context.DeadlineExceeded { 132 t.Fatal("New LRS stream created when expected to use an existing one") 133 } 134 135 // Push more loads. 136 store2.PerCluster("cluster2", "eds2").CallDropped("test") 137 138 // Ensure that loads are seen on the server. We need a loop here because 139 // there could have been some requests from the client in the time between 140 // us reading the first request and now. Those would have been queued in the 141 // request channel that we read out of. 142 for { 143 if ctx.Err() != nil { 144 t.Fatalf("Timeout when waiting for new loads to be seen on the server") 145 } 146 147 req, err = mgmtServer.LRSRequestChan.Receive(ctx) 148 if err != nil { 149 continue 150 } 151 gotLoad = req.(*fakeserver.Request).Req.(*v3lrspb.LoadStatsRequest).ClusterStats 152 if l := len(gotLoad); l != 1 { 153 continue 154 } 155 gotLoad[0].LoadReportInterval = nil 156 wantLoad := &v3endpointpb.ClusterStats{ 157 ClusterName: "cluster2", 158 ClusterServiceName: "eds2", 159 TotalDroppedRequests: 1, 160 DroppedRequests: []*v3endpointpb.ClusterStats_DroppedRequests{{Category: "test", DroppedCount: 1}}, 161 } 162 if diff := cmp.Diff(wantLoad, gotLoad[0], protocmp.Transform()); diff != "" { 163 t.Logf("Unexpected diff in LRS request (-got, +want):\n%s", diff) 164 continue 165 } 166 break 167 } 168 169 // Cancel the first load reporting call, and ensure that the stream does not 170 // close (because we have aother call open). 171 cancelLRS1() 172 sCtx, sCancel = context.WithTimeout(context.Background(), defaultTestShortTimeout) 173 defer sCancel() 174 if _, err := mgmtServer.LRSStreamCloseChan.Receive(sCtx); err != context.DeadlineExceeded { 175 t.Fatal("LRS stream closed when expected to stay open") 176 } 177 178 // Cancel the second load reporting call, and ensure the stream is closed. 179 cancelLRS2() 180 if _, err := mgmtServer.LRSStreamCloseChan.Receive(ctx); err != nil { 181 t.Fatal("Timeout waiting for LRS stream to close") 182 } 183 184 // Calling the load reporting API again should result in the creation of a 185 // new LRS stream. This ensures that creating and closing multiple streams 186 // works smoothly. 187 _, cancelLRS3 := tr.ReportLoad() 188 if err != nil { 189 t.Fatalf("Failed to start LRS load reporting: %v", err) 190 } 191 if _, err := mgmtServer.LRSStreamOpenChan.Receive(ctx); err != nil { 192 t.Fatalf("Timeout when waiting for LRS stream to be created: %v", err) 193 } 194 cancelLRS3() 195 }