istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/xds/debug_test.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package xds_test 16 17 import ( 18 "encoding/json" 19 "fmt" 20 "net/http" 21 "net/http/httptest" 22 "testing" 23 24 discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3" 25 26 "istio.io/istio/istioctl/pkg/util/configdump" 27 "istio.io/istio/pilot/pkg/model" 28 "istio.io/istio/pilot/pkg/xds" 29 v3 "istio.io/istio/pilot/pkg/xds/v3" 30 xdsfake "istio.io/istio/pilot/test/xds" 31 ) 32 33 func TestSyncz(t *testing.T) { 34 t.Run("return the sent and ack status of adsClient connections", func(t *testing.T) { 35 s := xdsfake.NewFakeDiscoveryServer(t, xdsfake.FakeOptions{}) 36 ads := s.ConnectADS() 37 38 ads.RequestResponseAck(t, &discovery.DiscoveryRequest{TypeUrl: v3.ClusterType}) 39 ads.RequestResponseAck(t, &discovery.DiscoveryRequest{TypeUrl: v3.ListenerType}) 40 ads.RequestResponseAck(t, &discovery.DiscoveryRequest{ 41 TypeUrl: v3.EndpointType, 42 ResourceNames: []string{"outbound|9080||app2.default.svc.cluster.local"}, 43 }) 44 ads.RequestResponseAck(t, &discovery.DiscoveryRequest{ 45 TypeUrl: v3.RouteType, 46 ResourceNames: []string{"80", "8080"}, 47 }) 48 49 node, _ := model.ParseServiceNodeWithMetadata(ads.ID, &model.NodeMetadata{}) 50 verifySyncStatus(t, s.Discovery, node.ID, true, true) 51 }) 52 t.Run("sync status not set when Nackd", func(t *testing.T) { 53 s := xdsfake.NewFakeDiscoveryServer(t, xdsfake.FakeOptions{}) 54 ads := s.ConnectADS() 55 56 ads.RequestResponseNack(t, &discovery.DiscoveryRequest{TypeUrl: v3.ClusterType}) 57 ads.RequestResponseNack(t, &discovery.DiscoveryRequest{TypeUrl: v3.ListenerType}) 58 ads.RequestResponseNack(t, &discovery.DiscoveryRequest{ 59 TypeUrl: v3.EndpointType, 60 ResourceNames: []string{"outbound|9080||app2.default.svc.cluster.local"}, 61 }) 62 ads.RequestResponseNack(t, &discovery.DiscoveryRequest{ 63 TypeUrl: v3.RouteType, 64 ResourceNames: []string{"80", "8080"}, 65 }) 66 node, _ := model.ParseServiceNodeWithMetadata(ads.ID, &model.NodeMetadata{}) 67 verifySyncStatus(t, s.Discovery, node.ID, true, false) 68 }) 69 t.Run("sync ecds", func(t *testing.T) { 70 s := xdsfake.NewFakeDiscoveryServer(t, xdsfake.FakeOptions{ 71 ConfigString: mustReadFile(t, "./testdata/ecds.yaml"), 72 }) 73 ads := s.ConnectADS() 74 75 ads.RequestResponseNack(t, &discovery.DiscoveryRequest{ 76 TypeUrl: v3.ExtensionConfigurationType, 77 ResourceNames: []string{"extension-config"}, 78 }) 79 node, _ := model.ParseServiceNodeWithMetadata(ads.ID, &model.NodeMetadata{}) 80 verifySyncStatus(t, s.Discovery, node.ID, true, true) 81 }) 82 } 83 84 func getSyncStatus(t *testing.T, server *xds.DiscoveryServer) []xds.SyncStatus { 85 req, err := http.NewRequest(http.MethodGet, "/debug", nil) 86 if err != nil { 87 t.Fatal(err) 88 } 89 rr := httptest.NewRecorder() 90 syncz := http.HandlerFunc(server.Syncz) 91 syncz.ServeHTTP(rr, req) 92 got := []xds.SyncStatus{} 93 if err := json.Unmarshal(rr.Body.Bytes(), &got); err != nil { 94 t.Error(err) 95 } 96 return got 97 } 98 99 func verifySyncStatus(t *testing.T, s *xds.DiscoveryServer, nodeID string, wantSent, wantAcked bool) { 100 // This is a mostly horrible hack because the single pilot instance is shared across multiple tests 101 // This makes this test contaminated by others and gives it horrible timing windows 102 attempts := 5 103 for i := 0; i < attempts; i++ { 104 gotStatus := getSyncStatus(t, s) 105 var errorHandler func(string, ...any) 106 if i == attempts-1 { 107 errorHandler = t.Errorf 108 } else { 109 errorHandler = t.Logf 110 } 111 for _, ss := range gotStatus { 112 if ss.ProxyID == nodeID { 113 if ss.ProxyVersion == "" { 114 errorHandler("ProxyVersion should always be set for %v", nodeID) 115 } 116 if (ss.ClusterSent != "") != wantSent { 117 errorHandler("wanted ClusterSent set %v got %v for %v", wantSent, ss.ClusterSent, nodeID) 118 } 119 if (ss.ClusterAcked != "") != wantAcked { 120 errorHandler("wanted ClusterAcked set %v got %v for %v", wantAcked, ss.ClusterAcked, nodeID) 121 } 122 if (ss.ListenerSent != "") != wantSent { 123 errorHandler("wanted ListenerSent set %v got %v for %v", wantSent, ss.ListenerSent, nodeID) 124 } 125 if (ss.ListenerAcked != "") != wantAcked { 126 errorHandler("wanted ListenerAcked set %v got %v for %v", wantAcked, ss.ListenerAcked, nodeID) 127 } 128 if (ss.RouteSent != "") != wantSent { 129 errorHandler("wanted RouteSent set %v got %v for %v", wantSent, ss.RouteSent, nodeID) 130 } 131 if (ss.RouteAcked != "") != wantAcked { 132 errorHandler("wanted RouteAcked set %v got %v for %v", wantAcked, ss.RouteAcked, nodeID) 133 } 134 if (ss.EndpointSent != "") != wantSent { 135 errorHandler("wanted EndpointSent set %v got %v for %v", wantSent, ss.EndpointSent, nodeID) 136 } 137 if (ss.EndpointAcked != "") != wantAcked { 138 errorHandler("wanted EndpointAcked set %v got %v for %v", wantAcked, ss.EndpointAcked, nodeID) 139 } 140 if (ss.ExtensionConfigSent != "") != wantSent { 141 errorHandler("wanted ExtesionConfigSent set %v got %v for %v", wantSent, ss.ExtensionConfigSent, nodeID) 142 } 143 if (ss.ExtensionConfigAcked != "") != wantAcked { 144 errorHandler("wanted ExtensionConfigAcked set %v got %v for %v", wantAcked, ss.ExtensionConfigAcked, nodeID) 145 } 146 return 147 } 148 } 149 errorHandler("node id %v not found", nodeID) 150 } 151 } 152 153 func TestConfigDump(t *testing.T) { 154 tests := []struct { 155 name string 156 wantCode int 157 proxyID string 158 }{ 159 { 160 name: "dumps most recent proxy with 200", 161 proxyID: "test.default", 162 wantCode: 200, 163 }, 164 { 165 name: "returns 404 if proxy not found", 166 proxyID: "not-found", 167 wantCode: 404, 168 }, 169 { 170 name: "returns 400 if no proxyID", 171 proxyID: "", 172 wantCode: 400, 173 }, 174 } 175 for _, tt := range tests { 176 t.Run(tt.name, func(t *testing.T) { 177 s := xdsfake.NewFakeDiscoveryServer(t, xdsfake.FakeOptions{}) 178 ads := s.ConnectADS() 179 ads.RequestResponseAck(t, &discovery.DiscoveryRequest{TypeUrl: v3.ClusterType}) 180 ads.RequestResponseAck(t, &discovery.DiscoveryRequest{TypeUrl: v3.ListenerType}) 181 ads.RequestResponseAck(t, &discovery.DiscoveryRequest{ 182 TypeUrl: v3.RouteType, 183 ResourceNames: []string{"80", "8080"}, 184 }) 185 186 wrapper := getConfigDump(t, s.Discovery, tt.proxyID, tt.wantCode) 187 if wrapper != nil { 188 if rs, err := wrapper.GetDynamicRouteDump(false); err != nil || len(rs.DynamicRouteConfigs) == 0 { 189 t.Errorf("routes were present, must have received an older connection's dump, err: %v", err) 190 } 191 } else if tt.wantCode < 400 { 192 t.Error("expected a non-nil wrapper with successful status code") 193 } 194 }) 195 } 196 } 197 198 func getConfigDump(t *testing.T, s *xds.DiscoveryServer, proxyID string, wantCode int) *configdump.Wrapper { 199 path := "/config_dump" 200 if proxyID != "" { 201 path += fmt.Sprintf("?proxyID=%v", proxyID) 202 } 203 req, err := http.NewRequest(http.MethodGet, path, nil) 204 if err != nil { 205 t.Fatal(err) 206 } 207 rr := httptest.NewRecorder() 208 syncz := http.HandlerFunc(s.ConfigDump) 209 syncz.ServeHTTP(rr, req) 210 if rr.Code != wantCode { 211 t.Errorf("wanted response code %v, got %v", wantCode, rr.Code) 212 } 213 if wantCode > 399 { 214 return nil 215 } 216 got := &configdump.Wrapper{} 217 if err := got.UnmarshalJSON(rr.Body.Bytes()); err != nil { 218 t.Fatalf(err.Error()) 219 } 220 return got 221 } 222 223 func TestDebugHandlers(t *testing.T) { 224 s := xdsfake.NewFakeDiscoveryServer(t, xdsfake.FakeOptions{}) 225 req, err := http.NewRequest(http.MethodGet, "/debug", nil) 226 if err != nil { 227 t.Fatal(err) 228 } 229 rr := httptest.NewRecorder() 230 debug := http.HandlerFunc(s.Discovery.Debug) 231 debug.ServeHTTP(rr, req) 232 if rr.Code != 200 { 233 t.Errorf("Error in generatating debug endpoint list") 234 } 235 }