istio.io/istio@v0.0.0-20240520182934-d79c90f27776/istioctl/pkg/writer/pilot/status.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 pilot
    16  
    17  import (
    18  	"encoding/json"
    19  	"fmt"
    20  	"io"
    21  	"sort"
    22  	"text/tabwriter"
    23  
    24  	discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
    25  	xdsstatus "github.com/envoyproxy/go-control-plane/envoy/service/status/v3"
    26  
    27  	"istio.io/istio/istioctl/pkg/multixds"
    28  	"istio.io/istio/pilot/pkg/model"
    29  	xdsresource "istio.io/istio/pilot/pkg/xds/v3"
    30  	"istio.io/istio/pkg/log"
    31  )
    32  
    33  // XdsStatusWriter enables printing of sync status using multiple xdsapi.DiscoveryResponse Istiod responses
    34  type XdsStatusWriter struct {
    35  	Writer                 io.Writer
    36  	Namespace              string
    37  	InternalDebugAllIstiod bool
    38  }
    39  
    40  type xdsWriterStatus struct {
    41  	proxyID               string
    42  	clusterID             string
    43  	istiodID              string
    44  	istiodVersion         string
    45  	clusterStatus         string
    46  	listenerStatus        string
    47  	routeStatus           string
    48  	endpointStatus        string
    49  	extensionconfigStatus string
    50  }
    51  
    52  const ignoredStatus = "IGNORED"
    53  
    54  // PrintAll takes a slice of Istiod syncz responses and outputs them using a tabwriter
    55  func (s *XdsStatusWriter) PrintAll(statuses map[string]*discovery.DiscoveryResponse) error {
    56  	w, fullStatus, err := s.setupStatusPrint(statuses)
    57  	if err != nil {
    58  		return err
    59  	}
    60  	for _, status := range fullStatus {
    61  		if err := xdsStatusPrintln(w, status); err != nil {
    62  			return err
    63  		}
    64  	}
    65  	if w != nil {
    66  		return w.Flush()
    67  	}
    68  	return nil
    69  }
    70  
    71  func (s *XdsStatusWriter) setupStatusPrint(drs map[string]*discovery.DiscoveryResponse) (*tabwriter.Writer, []*xdsWriterStatus, error) {
    72  	// Gather the statuses before printing so they may be sorted
    73  	var fullStatus []*xdsWriterStatus
    74  	mappedResp := map[string]string{}
    75  	w := new(tabwriter.Writer).Init(s.Writer, 0, 8, 5, ' ', 0)
    76  	_, _ = fmt.Fprintln(w, "NAME\tCLUSTER\tCDS\tLDS\tEDS\tRDS\tECDS\tISTIOD\tVERSION")
    77  	for _, dr := range drs {
    78  		for _, resource := range dr.Resources {
    79  			clientConfig := xdsstatus.ClientConfig{}
    80  			err := resource.UnmarshalTo(&clientConfig)
    81  			if err != nil {
    82  				return nil, nil, fmt.Errorf("could not unmarshal ClientConfig: %w", err)
    83  			}
    84  			meta, err := model.ParseMetadata(clientConfig.GetNode().GetMetadata())
    85  			if err != nil {
    86  				return nil, nil, fmt.Errorf("could not parse node metadata: %w", err)
    87  			}
    88  			if s.Namespace != "" && meta.Namespace != s.Namespace {
    89  				continue
    90  			}
    91  			cds, lds, eds, rds, ecds := getSyncStatus(&clientConfig)
    92  			cp := multixds.CpInfo(dr)
    93  			fullStatus = append(fullStatus, &xdsWriterStatus{
    94  				proxyID:               clientConfig.GetNode().GetId(),
    95  				clusterID:             meta.ClusterID.String(),
    96  				istiodID:              cp.ID,
    97  				istiodVersion:         meta.IstioVersion,
    98  				clusterStatus:         cds,
    99  				listenerStatus:        lds,
   100  				routeStatus:           rds,
   101  				endpointStatus:        eds,
   102  				extensionconfigStatus: ecds,
   103  			})
   104  			if len(fullStatus) == 0 {
   105  				return nil, nil, fmt.Errorf("no proxies found (checked %d istiods)", len(drs))
   106  			}
   107  
   108  			sort.Slice(fullStatus, func(i, j int) bool {
   109  				return fullStatus[i].proxyID < fullStatus[j].proxyID
   110  			})
   111  		}
   112  	}
   113  	if len(mappedResp) > 0 {
   114  		mresp, err := json.MarshalIndent(mappedResp, "", "  ")
   115  		if err != nil {
   116  			return nil, nil, err
   117  		}
   118  		_, _ = s.Writer.Write(mresp)
   119  		_, _ = s.Writer.Write([]byte("\n"))
   120  	}
   121  
   122  	return w, fullStatus, nil
   123  }
   124  
   125  func xdsStatusPrintln(w io.Writer, status *xdsWriterStatus) error {
   126  	_, err := fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\n",
   127  		status.proxyID, status.clusterID,
   128  		status.clusterStatus, status.listenerStatus, status.endpointStatus, status.routeStatus,
   129  		status.extensionconfigStatus,
   130  		status.istiodID, status.istiodVersion)
   131  	return err
   132  }
   133  
   134  func formatStatus(s *xdsstatus.ClientConfig_GenericXdsConfig) string {
   135  	switch s.GetConfigStatus() {
   136  	case xdsstatus.ConfigStatus_UNKNOWN:
   137  		return ignoredStatus
   138  	case xdsstatus.ConfigStatus_NOT_SENT:
   139  		return "NOT SENT"
   140  	default:
   141  		return s.GetConfigStatus().String()
   142  	}
   143  }
   144  
   145  func getSyncStatus(clientConfig *xdsstatus.ClientConfig) (cds, lds, eds, rds, ecds string) {
   146  	configs := handleAndGetXdsConfigs(clientConfig)
   147  	for _, config := range configs {
   148  		cfgType := config.GetTypeUrl()
   149  		switch cfgType {
   150  		case xdsresource.ListenerType:
   151  			lds = formatStatus(config)
   152  		case xdsresource.ClusterType:
   153  			cds = formatStatus(config)
   154  		case xdsresource.RouteType:
   155  			rds = formatStatus(config)
   156  		case xdsresource.EndpointType:
   157  			eds = formatStatus(config)
   158  		case xdsresource.ExtensionConfigurationType:
   159  			ecds = formatStatus(config)
   160  		default:
   161  			log.Infof("GenericXdsConfig unexpected type %s\n", xdsresource.GetShortType(cfgType))
   162  		}
   163  	}
   164  	return
   165  }
   166  
   167  func handleAndGetXdsConfigs(clientConfig *xdsstatus.ClientConfig) []*xdsstatus.ClientConfig_GenericXdsConfig {
   168  	configs := make([]*xdsstatus.ClientConfig_GenericXdsConfig, 0)
   169  	if clientConfig.GetGenericXdsConfigs() != nil {
   170  		configs = clientConfig.GetGenericXdsConfigs()
   171  		return configs
   172  	}
   173  
   174  	// FIXME: currently removing the deprecated code below may result in functions not working
   175  	// if there is a mismatch of versions between istiod and istioctl
   176  	// nolint: staticcheck
   177  	for _, config := range clientConfig.GetXdsConfig() {
   178  		var typeURL string
   179  		switch config.PerXdsConfig.(type) {
   180  		case *xdsstatus.PerXdsConfig_ListenerConfig:
   181  			typeURL = xdsresource.ListenerType
   182  		case *xdsstatus.PerXdsConfig_ClusterConfig:
   183  			typeURL = xdsresource.ClusterType
   184  		case *xdsstatus.PerXdsConfig_RouteConfig:
   185  			typeURL = xdsresource.RouteType
   186  		case *xdsstatus.PerXdsConfig_EndpointConfig:
   187  			typeURL = xdsresource.EndpointType
   188  		}
   189  
   190  		if typeURL != "" {
   191  			configs = append(configs, &xdsstatus.ClientConfig_GenericXdsConfig{
   192  				TypeUrl:      typeURL,
   193  				ConfigStatus: config.Status,
   194  			})
   195  		}
   196  	}
   197  
   198  	return configs
   199  }