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  }