google.golang.org/grpc@v1.72.2/xds/internal/server/rds_handler_test.go (about) 1 /* 2 * 3 * Copyright 2021 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 19 package server 20 21 import ( 22 "context" 23 "fmt" 24 "testing" 25 "time" 26 27 "github.com/google/go-cmp/cmp" 28 "github.com/google/go-cmp/cmp/cmpopts" 29 "github.com/google/uuid" 30 "google.golang.org/grpc/internal/grpctest" 31 "google.golang.org/grpc/internal/testutils/xds/e2e" 32 "google.golang.org/grpc/internal/xds/bootstrap" 33 "google.golang.org/grpc/xds/internal/xdsclient" 34 "google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version" 35 36 v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" 37 v3discoverypb "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3" 38 ) 39 40 type s struct { 41 grpctest.Tester 42 } 43 44 func Test(t *testing.T) { 45 grpctest.RunSubTests(t, s{}) 46 } 47 48 const ( 49 defaultTestTimeout = 10 * time.Second 50 defaultTestShortTimeout = 10 * time.Millisecond // For events expected to *not* happen. 51 ) 52 53 const ( 54 listenerName = "listener" 55 clusterName = "cluster" 56 57 route1 = "route1" 58 route2 = "route2" 59 route3 = "route3" 60 route4 = "route4" 61 ) 62 63 // xdsSetupForTests performs the following setup actions: 64 // - spins up an xDS management server 65 // - creates an xDS client with a bootstrap configuration pointing to the above 66 // management server 67 // 68 // Returns the following: 69 // - a reference to the management server 70 // - nodeID to use when pushing resources to the management server 71 // - a channel to read lds resource names received by the management server 72 // - a channel to read rds resource names received by the management server 73 // - an xDS client to pass to the rdsHandler under test 74 func xdsSetupForTests(t *testing.T) (*e2e.ManagementServer, string, chan []string, chan []string, xdsclient.XDSClient) { 75 t.Helper() 76 77 ldsNamesCh := make(chan []string, 1) 78 rdsNamesCh := make(chan []string, 1) 79 80 // Setup the management server to push the requested route configuration 81 // resource names on to a channel for the test to inspect. 82 mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{ 83 OnStreamRequest: func(_ int64, req *v3discoverypb.DiscoveryRequest) error { 84 switch req.GetTypeUrl() { 85 case version.V3ListenerURL: // Waits on the listener, and route config below... 86 select { 87 case <-ldsNamesCh: 88 default: 89 } 90 select { 91 case ldsNamesCh <- req.GetResourceNames(): 92 default: 93 } 94 case version.V3RouteConfigURL: // waits on route config names here... 95 select { 96 case <-rdsNamesCh: 97 default: 98 } 99 select { 100 case rdsNamesCh <- req.GetResourceNames(): 101 default: 102 } 103 default: 104 return fmt.Errorf("unexpected resources %v of type %q requested", req.GetResourceNames(), req.GetTypeUrl()) 105 } 106 return nil 107 }, 108 AllowResourceSubset: true, 109 }) 110 111 // Create bootstrap configuration pointing to the above management server. 112 nodeID := uuid.New().String() 113 bootstrapContents := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address) 114 115 config, err := bootstrap.NewConfigFromContents(bootstrapContents) 116 if err != nil { 117 t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err) 118 } 119 pool := xdsclient.NewPool(config) 120 xdsC, cancel, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{ 121 Name: t.Name(), 122 }) 123 if err != nil { 124 t.Fatal(err) 125 } 126 t.Cleanup(cancel) 127 128 return mgmtServer, nodeID, ldsNamesCh, rdsNamesCh, xdsC 129 } 130 131 // Waits for the wantNames to be pushed on to namesCh. Fails the test by calling 132 // t.Fatal if the context expires before that. 133 func waitForResourceNames(ctx context.Context, t *testing.T, namesCh chan []string, wantNames []string) { 134 t.Helper() 135 136 for ; ctx.Err() == nil; <-time.After(defaultTestShortTimeout) { 137 select { 138 case <-ctx.Done(): 139 case gotNames := <-namesCh: 140 if cmp.Equal(gotNames, wantNames, cmpopts.EquateEmpty(), cmpopts.SortSlices(func(s1, s2 string) bool { return s1 < s2 })) { 141 return 142 } 143 t.Logf("Received resource names %v, want %v", gotNames, wantNames) 144 } 145 } 146 t.Fatalf("Timeout waiting for resource to be requested from the management server") 147 } 148 149 func routeConfigResourceForName(name string) *v3routepb.RouteConfiguration { 150 return e2e.RouteConfigResourceWithOptions(e2e.RouteConfigOptions{ 151 RouteConfigName: name, 152 ListenerName: listenerName, 153 ClusterSpecifierType: e2e.RouteConfigClusterSpecifierTypeCluster, 154 ClusterName: clusterName, 155 }) 156 } 157 158 type testCallbackVerify struct { 159 ch chan callbackStruct 160 } 161 162 type callbackStruct struct { 163 routeName string 164 rwu rdsWatcherUpdate 165 } 166 167 func (tcv *testCallbackVerify) testCallback(routeName string, rwu rdsWatcherUpdate) { 168 tcv.ch <- callbackStruct{routeName: routeName, rwu: rwu} 169 } 170 171 func verifyRouteName(ctx context.Context, t *testing.T, ch chan callbackStruct, want callbackStruct) { 172 t.Helper() 173 select { 174 case got := <-ch: 175 if diff := cmp.Diff(got.routeName, want.routeName); diff != "" { 176 t.Fatalf("unexpected update received (-got, +want):%v, want: %v", got, want) 177 } 178 case <-ctx.Done(): 179 t.Fatalf("timeout waiting for callback") 180 } 181 } 182 183 // TestRDSHandler tests the RDS Handler. It first configures the rds handler to 184 // watch route 1 and 2. Before receiving both RDS updates, it should not be 185 // ready, but after receiving both, it should be ready. It then tells the rds 186 // handler to watch route 1 2 and 3. It should not be ready until it receives 187 // route3 from the management server. It then configures the rds handler to 188 // watch route 1 and 3. It should immediately be ready. It then configures the 189 // rds handler to watch route 1 and 4. It should not be ready until it receives 190 // an rds update for route 4. 191 func (s) TestRDSHandler(t *testing.T) { 192 mgmtServer, nodeID, _, rdsNamesCh, xdsC := xdsSetupForTests(t) 193 194 ch := make(chan callbackStruct, 1) 195 tcv := &testCallbackVerify{ch: ch} 196 rh := newRDSHandler(tcv.testCallback, xdsC, nil) 197 198 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 199 defer cancel() 200 201 // Configure the management server with a single route config resource. 202 routeResource1 := routeConfigResourceForName(route1) 203 resources := e2e.UpdateOptions{ 204 NodeID: nodeID, 205 Routes: []*v3routepb.RouteConfiguration{routeResource1}, 206 SkipValidation: true, 207 } 208 if err := mgmtServer.Update(ctx, resources); err != nil { 209 t.Fatal(err) 210 } 211 212 rh.updateRouteNamesToWatch(map[string]bool{route1: true, route2: true}) 213 waitForResourceNames(ctx, t, rdsNamesCh, []string{route1, route2}) 214 verifyRouteName(ctx, t, ch, callbackStruct{routeName: route1}) 215 216 // The rds handler update should not be ready. 217 if got := rh.determineRouteConfigurationReady(); got != false { 218 t.Fatalf("rh.determineRouteConfigurationReady: %v, want: false", false) 219 } 220 221 // Configure the management server both route config resources. 222 routeResource2 := routeConfigResourceForName(route2) 223 resources.Routes = []*v3routepb.RouteConfiguration{routeResource1, routeResource2} 224 if err := mgmtServer.Update(ctx, resources); err != nil { 225 t.Fatal(err) 226 } 227 228 verifyRouteName(ctx, t, ch, callbackStruct{routeName: route2}) 229 230 if got := rh.determineRouteConfigurationReady(); got != true { 231 t.Fatalf("rh.determineRouteConfigurationReady: %v, want: true", got) 232 } 233 234 rh.updateRouteNamesToWatch(map[string]bool{route1: true, route2: true, route3: true}) 235 waitForResourceNames(ctx, t, rdsNamesCh, []string{route1, route2, route3}) 236 if got := rh.determineRouteConfigurationReady(); got != false { 237 t.Fatalf("rh.determineRouteConfigurationReady: %v, want: false", got) 238 } 239 240 // Configure the management server with route config resources. 241 routeResource3 := routeConfigResourceForName(route3) 242 resources.Routes = []*v3routepb.RouteConfiguration{routeResource1, routeResource2, routeResource3} 243 if err := mgmtServer.Update(ctx, resources); err != nil { 244 t.Fatal(err) 245 } 246 verifyRouteName(ctx, t, ch, callbackStruct{routeName: route3}) 247 248 if got := rh.determineRouteConfigurationReady(); got != true { 249 t.Fatalf("rh.determineRouteConfigurationReady: %v, want: true", got) 250 } 251 // Update to route 1 and route 2. Should immediately go ready. 252 rh.updateRouteNamesToWatch(map[string]bool{route1: true, route3: true}) 253 if got := rh.determineRouteConfigurationReady(); got != true { 254 t.Fatalf("rh.determineRouteConfigurationReady: %v, want: true", got) 255 } 256 257 // Update to route 1 and route 4. No route 4, so should not be ready. 258 rh.updateRouteNamesToWatch(map[string]bool{route1: true, route4: true}) 259 waitForResourceNames(ctx, t, rdsNamesCh, []string{route1, route4}) 260 if got := rh.determineRouteConfigurationReady(); got != false { 261 t.Fatalf("rh.determineRouteConfigurationReady: %v, want: false", got) 262 } 263 routeResource4 := routeConfigResourceForName(route4) 264 resources.Routes = []*v3routepb.RouteConfiguration{routeResource1, routeResource2, routeResource3, routeResource4} 265 if err := mgmtServer.Update(ctx, resources); err != nil { 266 t.Fatal(err) 267 } 268 verifyRouteName(ctx, t, ch, callbackStruct{routeName: route4}) 269 if got := rh.determineRouteConfigurationReady(); got != true { 270 t.Fatalf("rh.determineRouteConfigurationReady: %v, want: true", got) 271 } 272 }