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  }