istio.io/istio@v0.0.0-20240520182934-d79c90f27776/istioctl/pkg/writer/envoy/clusters/clusters.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 clusters 16 17 import ( 18 "encoding/json" 19 "fmt" 20 "io" 21 "sort" 22 "strconv" 23 "strings" 24 "text/tabwriter" 25 26 admin "github.com/envoyproxy/go-control-plane/envoy/admin/v3" 27 core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" 28 "sigs.k8s.io/yaml" 29 30 "istio.io/istio/istioctl/pkg/util/clusters" 31 "istio.io/istio/istioctl/pkg/util/proto" 32 ) 33 34 // EndpointFilter is used to pass filter information into route based config writer print functions 35 type EndpointFilter struct { 36 Address string 37 Port uint32 38 Cluster string 39 Status string 40 } 41 42 // ConfigWriter is a writer for processing responses from the Envoy Admin config_dump endpoint 43 type ConfigWriter struct { 44 Stdout io.Writer 45 clusters *clusters.Wrapper 46 } 47 48 // EndpointCluster is used to store the endpoint and cluster 49 type EndpointCluster struct { 50 address string 51 port int 52 cluster string 53 status core.HealthStatus 54 failedOutlierCheck bool 55 } 56 57 // Prime loads the clusters output into the writer ready for printing 58 func (c *ConfigWriter) Prime(b []byte) error { 59 cd := clusters.Wrapper{} 60 err := json.Unmarshal(b, &cd) 61 if err != nil { 62 return fmt.Errorf("error unmarshalling config dump response from Envoy: %v", err) 63 } 64 c.clusters = &cd 65 return nil 66 } 67 68 func retrieveEndpointAddress(host *admin.HostStatus) string { 69 addr := host.Address.GetSocketAddress() 70 if addr != nil { 71 return addr.Address 72 } 73 if pipe := host.Address.GetPipe(); pipe != nil { 74 return "unix://" + pipe.Path 75 } 76 if internal := host.Address.GetEnvoyInternalAddress(); internal != nil { 77 switch an := internal.GetAddressNameSpecifier().(type) { 78 case *core.EnvoyInternalAddress_ServerListenerName: 79 return fmt.Sprintf("envoy://%s/%s", an.ServerListenerName, internal.EndpointId) 80 } 81 } 82 return "unknown" 83 } 84 85 func retrieveEndpointPort(l *admin.HostStatus) uint32 { 86 addr := l.Address.GetSocketAddress() 87 if addr != nil { 88 return addr.GetPortValue() 89 } 90 return 0 91 } 92 93 func retrieveEndpointStatus(l *admin.HostStatus) core.HealthStatus { 94 return l.HealthStatus.GetEdsHealthStatus() 95 } 96 97 func retrieveFailedOutlierCheck(l *admin.HostStatus) bool { 98 return l.HealthStatus.GetFailedOutlierCheck() 99 } 100 101 // Verify returns true if the passed host matches the filter fields 102 func (e *EndpointFilter) Verify(host *admin.HostStatus, cluster string) bool { 103 if e.Address == "" && e.Port == 0 && e.Cluster == "" && e.Status == "" { 104 return true 105 } 106 if e.Address != "" && !strings.EqualFold(retrieveEndpointAddress(host), e.Address) { 107 return false 108 } 109 if e.Port != 0 && retrieveEndpointPort(host) != e.Port { 110 return false 111 } 112 if e.Cluster != "" && !strings.EqualFold(cluster, e.Cluster) { 113 return false 114 } 115 status := retrieveEndpointStatus(host) 116 if e.Status != "" && !strings.EqualFold(core.HealthStatus_name[int32(status)], e.Status) { 117 return false 118 } 119 return true 120 } 121 122 // PrintEndpointsSummary prints just the endpoints config summary to the ConfigWriter stdout 123 func (c *ConfigWriter) PrintEndpointsSummary(filter EndpointFilter) error { 124 if c.clusters == nil { 125 return fmt.Errorf("config writer has not been primed") 126 } 127 128 w := new(tabwriter.Writer).Init(c.Stdout, 0, 8, 5, ' ', 0) 129 130 clusterEndpoint := make([]EndpointCluster, 0) 131 for _, cluster := range c.clusters.ClusterStatuses { 132 for _, host := range cluster.HostStatuses { 133 if filter.Verify(host, cluster.Name) { 134 addr := retrieveEndpointAddress(host) 135 port := retrieveEndpointPort(host) 136 status := retrieveEndpointStatus(host) 137 outlierCheck := retrieveFailedOutlierCheck(host) 138 clusterEndpoint = append(clusterEndpoint, EndpointCluster{addr, int(port), cluster.Name, status, outlierCheck}) 139 } 140 } 141 } 142 143 clusterEndpoint = retrieveSortedEndpointClusterSlice(clusterEndpoint) 144 fmt.Fprintln(w, "ENDPOINT\tSTATUS\tOUTLIER CHECK\tCLUSTER") 145 for _, ce := range clusterEndpoint { 146 var endpoint string 147 if ce.port != 0 { 148 endpoint = ce.address + ":" + strconv.Itoa(ce.port) 149 } else { 150 endpoint = ce.address 151 } 152 fmt.Fprintf(w, "%v\t%v\t%v\t%v\n", endpoint, core.HealthStatus_name[int32(ce.status)], printFailedOutlierCheck(ce.failedOutlierCheck), ce.cluster) 153 } 154 155 return w.Flush() 156 } 157 158 // PrintEndpoints prints the endpoints config to the ConfigWriter stdout 159 func (c *ConfigWriter) PrintEndpoints(filter EndpointFilter, outputFormat string) error { 160 if c.clusters == nil { 161 return fmt.Errorf("config writer has not been primed") 162 } 163 164 filteredClusters := proto.MessageSlice{} 165 for _, cluster := range c.clusters.ClusterStatuses { 166 for _, host := range cluster.HostStatuses { 167 if filter.Verify(host, cluster.Name) { 168 filteredClusters = append(filteredClusters, cluster) 169 break 170 } 171 } 172 } 173 out, err := json.MarshalIndent(filteredClusters, "", " ") 174 if err != nil { 175 return err 176 } 177 if outputFormat == "yaml" { 178 if out, err = yaml.JSONToYAML(out); err != nil { 179 return err 180 } 181 } 182 fmt.Fprintln(c.Stdout, string(out)) 183 return nil 184 } 185 186 func retrieveSortedEndpointClusterSlice(ec []EndpointCluster) []EndpointCluster { 187 sort.Slice(ec, func(i, j int) bool { 188 if ec[i].address == ec[j].address { 189 if ec[i].port == ec[j].port { 190 return ec[i].cluster < ec[j].cluster 191 } 192 return ec[i].port < ec[j].port 193 } 194 return ec[i].address < ec[j].address 195 }) 196 return ec 197 } 198 199 func printFailedOutlierCheck(b bool) string { 200 if b { 201 return "FAILED" 202 } 203 return "OK" 204 }