google.golang.org/grpc@v1.72.2/xds/internal/xdsclient/tests/ads_stream_watch_test.go (about) 1 /* 2 * 3 * Copyright 2024 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 xdsclient_test 20 21 import ( 22 "context" 23 "fmt" 24 "testing" 25 "time" 26 27 "github.com/google/uuid" 28 "google.golang.org/grpc/internal/testutils" 29 "google.golang.org/grpc/internal/testutils/xds/e2e" 30 "google.golang.org/grpc/internal/xds/bootstrap" 31 xdsinternal "google.golang.org/grpc/xds/internal" 32 "google.golang.org/grpc/xds/internal/xdsclient" 33 "google.golang.org/grpc/xds/internal/xdsclient/internal" 34 "google.golang.org/grpc/xds/internal/xdsclient/transport/ads" 35 "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" 36 "google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version" 37 38 v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" 39 ) 40 41 // Tests the state transitions of the resource specific watch state within the 42 // ADS stream, specifically when the stream breaks (for both resources that have 43 // been previously received and for resources that are yet to be received). 44 func (s) TestADS_WatchState_StreamBreaks(t *testing.T) { 45 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 46 defer cancel() 47 48 // Create an xDS management server with a restartable listener. 49 l, err := testutils.LocalTCPListener() 50 if err != nil { 51 t.Fatalf("Failed to create a local listener for the xDS management server: %v", err) 52 } 53 lis := testutils.NewRestartableListener(l) 54 mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{Listener: lis}) 55 56 // Create an xDS client with bootstrap pointing to the above server. 57 nodeID := uuid.New().String() 58 bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address) 59 client := createXDSClient(t, bc) 60 61 // Create a watch for the first listener resource and verify that the timer 62 // is running and the watch state is `requested`. 63 const listenerName1 = "listener1" 64 ldsCancel1 := xdsresource.WatchListener(client, listenerName1, noopListenerWatcher{}) 65 defer ldsCancel1() 66 if err := waitForResourceWatchState(ctx, client, listenerName1, ads.ResourceWatchStateRequested, true); err != nil { 67 t.Fatal(err) 68 } 69 70 // Configure the first resource on the management server. This should result 71 // in the resource being pushed to the xDS client and should result in the 72 // timer getting stopped and the watch state moving to `received`. 73 const routeConfigName = "route-config" 74 listenerResource1 := e2e.DefaultClientListener(listenerName1, routeConfigName) 75 resources := e2e.UpdateOptions{ 76 NodeID: nodeID, 77 Listeners: []*v3listenerpb.Listener{listenerResource1}, 78 SkipValidation: true, 79 } 80 if err := mgmtServer.Update(ctx, resources); err != nil { 81 t.Fatal(err) 82 } 83 if err := waitForResourceWatchState(ctx, client, listenerName1, ads.ResourceWatchStateReceived, false); err != nil { 84 t.Fatal(err) 85 } 86 87 // Create a watch for the second listener resource and verify that the timer 88 // is running and the watch state is `requested`. 89 const listenerName2 = "listener2" 90 ldsCancel2 := xdsresource.WatchListener(client, listenerName2, noopListenerWatcher{}) 91 defer ldsCancel2() 92 if err := waitForResourceWatchState(ctx, client, listenerName2, ads.ResourceWatchStateRequested, true); err != nil { 93 t.Fatal(err) 94 } 95 96 // Stop the server to break the ADS stream. Since the first resource was 97 // already received, this should not change anything for it. But for the 98 // second resource, it should result in the timer getting stopped and the 99 // watch state moving to `started`. 100 lis.Stop() 101 if err := waitForResourceWatchState(ctx, client, listenerName2, ads.ResourceWatchStateStarted, false); err != nil { 102 t.Fatal(err) 103 } 104 if err := verifyResourceWatchState(client, listenerName1, ads.ResourceWatchStateReceived, false); err != nil { 105 t.Fatal(err) 106 } 107 108 // Restart the server and verify that the timer is running and the watch 109 // state is `requested`, for the second resource. For the first resource, 110 // nothing should change. 111 lis.Restart() 112 if err := waitForResourceWatchState(ctx, client, listenerName2, ads.ResourceWatchStateRequested, true); err != nil { 113 t.Fatal(err) 114 } 115 if err := verifyResourceWatchState(client, listenerName1, ads.ResourceWatchStateReceived, false); err != nil { 116 t.Fatal(err) 117 } 118 119 // Configure the second resource on the management server. This should result 120 // in the resource being pushed to the xDS client and should result in the 121 // timer getting stopped and the watch state moving to `received`. 122 listenerResource2 := e2e.DefaultClientListener(listenerName2, routeConfigName) 123 resources = e2e.UpdateOptions{ 124 NodeID: nodeID, 125 Listeners: []*v3listenerpb.Listener{listenerResource1, listenerResource2}, 126 SkipValidation: true, 127 } 128 if err := mgmtServer.Update(ctx, resources); err != nil { 129 t.Fatal(err) 130 } 131 if err := waitForResourceWatchState(ctx, client, listenerName2, ads.ResourceWatchStateReceived, false); err != nil { 132 t.Fatal(err) 133 } 134 } 135 136 // Tests the behavior of the xDS client when a resource watch timer expires and 137 // verifies the resource watch state transitions as expected. 138 func (s) TestADS_WatchState_TimerFires(t *testing.T) { 139 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 140 defer cancel() 141 142 // Start an xDS management server. 143 mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{}) 144 145 // Create an xDS client with bootstrap pointing to the above server, and a 146 // short resource expiry timeout. 147 nodeID := uuid.New().String() 148 bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address) 149 config, err := bootstrap.NewConfigFromContents(bc) 150 if err != nil { 151 t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bc), err) 152 } 153 pool := xdsclient.NewPool(config) 154 client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{ 155 Name: t.Name(), 156 WatchExpiryTimeout: defaultTestWatchExpiryTimeout, 157 }) 158 if err != nil { 159 t.Fatalf("Failed to create xDS client: %v", err) 160 } 161 defer close() 162 163 // Create a watch for the first listener resource and verify that the timer 164 // is running and the watch state is `requested`. 165 const listenerName = "listener" 166 ldsCancel1 := xdsresource.WatchListener(client, listenerName, noopListenerWatcher{}) 167 defer ldsCancel1() 168 if err := waitForResourceWatchState(ctx, client, listenerName, ads.ResourceWatchStateRequested, true); err != nil { 169 t.Fatal(err) 170 } 171 172 // Since the resource is not configured on the management server, the watch 173 // expiry timer is expected to fire, and the watch state should move to 174 // `timeout`. 175 if err := waitForResourceWatchState(ctx, client, listenerName, ads.ResourceWatchStateTimeout, false); err != nil { 176 t.Fatal(err) 177 } 178 } 179 180 func waitForResourceWatchState(ctx context.Context, client xdsclient.XDSClient, resourceName string, wantState ads.WatchState, wantTimer bool) error { 181 var lastErr error 182 for ; ctx.Err() == nil; <-time.After(defaultTestShortTimeout) { 183 err := verifyResourceWatchState(client, resourceName, wantState, wantTimer) 184 if err == nil { 185 break 186 } 187 lastErr = err 188 } 189 if ctx.Err() != nil { 190 return fmt.Errorf("timeout when waiting for expected watch state for resource %q: %v", resourceName, lastErr) 191 } 192 return nil 193 } 194 195 func verifyResourceWatchState(client xdsclient.XDSClient, resourceName string, wantState ads.WatchState, wantTimer bool) error { 196 resourceWatchStateForTesting := internal.ResourceWatchStateForTesting.(func(xdsclient.XDSClient, xdsresource.Type, string) (ads.ResourceWatchState, error)) 197 listenerResourceType := xdsinternal.ResourceTypeMapForTesting[version.V3ListenerURL].(xdsresource.Type) 198 gotState, err := resourceWatchStateForTesting(client, listenerResourceType, resourceName) 199 if err != nil { 200 return fmt.Errorf("failed to get watch state for resource %q: %v", resourceName, err) 201 } 202 if gotState.State != wantState { 203 return fmt.Errorf("watch state for resource %q is %v, want %v", resourceName, gotState.State, wantState) 204 } 205 if (gotState.ExpiryTimer != nil) != wantTimer { 206 return fmt.Errorf("expiry timer for resource %q is %t, want %t", resourceName, gotState.ExpiryTimer != nil, wantTimer) 207 } 208 return nil 209 }