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 }