istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/xds/statusgen.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
    16  
    17  import (
    18  	"fmt"
    19  
    20  	core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
    21  	discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
    22  	status "github.com/envoyproxy/go-control-plane/envoy/service/status/v3"
    23  
    24  	"istio.io/istio/pilot/pkg/model"
    25  	"istio.io/istio/pilot/pkg/util/protoconv"
    26  	v3 "istio.io/istio/pilot/pkg/xds/v3"
    27  )
    28  
    29  const (
    30  	TypeDebugPrefix = v3.DebugType + "/"
    31  
    32  	// TypeDebugSyncronization requests Envoy CSDS for proxy sync status
    33  	TypeDebugSyncronization = v3.DebugType + "/syncz"
    34  
    35  	// TypeDebugConfigDump requests Envoy configuration for a proxy without creating one
    36  	TypeDebugConfigDump = v3.DebugType + "/config_dump"
    37  
    38  	// TODO: TypeURLReady - readiness events for endpoints, agent can propagate
    39  )
    40  
    41  // StatusGen is a Generator for XDS status: connections, syncz, configdump
    42  type StatusGen struct {
    43  	Server *DiscoveryServer
    44  
    45  	// TODO: track last N Nacks and connection events, with 'version' based on timestamp.
    46  	// On new connect, use version to send recent events since last update.
    47  }
    48  
    49  func NewStatusGen(s *DiscoveryServer) *StatusGen {
    50  	return &StatusGen{
    51  		Server: s,
    52  	}
    53  }
    54  
    55  // Generate XDS responses about internal events:
    56  // - connection status
    57  // - NACKs
    58  // We can also expose ACKS.
    59  func (sg *StatusGen) Generate(proxy *model.Proxy, w *model.WatchedResource, req *model.PushRequest) (model.Resources, model.XdsLogDetails, error) {
    60  	return sg.handleInternalRequest(proxy, w, req)
    61  }
    62  
    63  // Generate delta XDS responses about internal events:
    64  // - connection status
    65  // - NACKs
    66  // We can also expose ACKS.
    67  func (sg *StatusGen) GenerateDeltas(
    68  	proxy *model.Proxy,
    69  	req *model.PushRequest,
    70  	w *model.WatchedResource,
    71  ) (model.Resources, model.DeletedResources, model.XdsLogDetails, bool, error) {
    72  	res, detail, err := sg.handleInternalRequest(proxy, w, req)
    73  	return res, nil, detail, true, err
    74  }
    75  
    76  func (sg *StatusGen) handleInternalRequest(_ *model.Proxy, w *model.WatchedResource, _ *model.PushRequest) (model.Resources, model.XdsLogDetails, error) {
    77  	res := model.Resources{}
    78  
    79  	switch w.TypeUrl {
    80  	case TypeDebugSyncronization:
    81  		res = sg.debugSyncz()
    82  	case TypeDebugConfigDump:
    83  		if len(w.ResourceNames) == 0 || len(w.ResourceNames) > 1 {
    84  			// Malformed request from client
    85  			log.Infof("%s with %d ResourceNames", TypeDebugConfigDump, len(w.ResourceNames))
    86  			break
    87  		}
    88  		var err error
    89  		dumpRes, err := sg.debugConfigDump(w.ResourceNames[0])
    90  		if err != nil {
    91  			log.Infof("%s failed: %v", TypeDebugConfigDump, err)
    92  			break
    93  		}
    94  		res = dumpRes
    95  	}
    96  	return res, model.DefaultXdsLogDetails, nil
    97  }
    98  
    99  // isSidecar ad-hoc method to see if connection represents a sidecar
   100  func isProxy(con *Connection) bool {
   101  	return con != nil &&
   102  		con.proxy != nil &&
   103  		con.proxy.Metadata != nil &&
   104  		con.proxy.Metadata.ProxyConfig != nil
   105  }
   106  
   107  func isZtunnel(con *Connection) bool {
   108  	return con != nil &&
   109  		con.proxy != nil &&
   110  		con.proxy.Metadata != nil &&
   111  		con.proxy.Type == model.Ztunnel
   112  }
   113  
   114  func (sg *StatusGen) debugSyncz() model.Resources {
   115  	res := model.Resources{}
   116  
   117  	stypes := []string{
   118  		v3.ListenerType,
   119  		v3.RouteType,
   120  		v3.EndpointType,
   121  		v3.ClusterType,
   122  		v3.ExtensionConfigurationType,
   123  	}
   124  
   125  	for _, con := range sg.Server.Clients() {
   126  		con.proxy.RLock()
   127  		// Skip "nodes" without metadata (they are probably istioctl queries!)
   128  		if isProxy(con) || isZtunnel(con) {
   129  			xdsConfigs := make([]*status.ClientConfig_GenericXdsConfig, 0)
   130  			for _, stype := range stypes {
   131  				pxc := &status.ClientConfig_GenericXdsConfig{}
   132  				if watchedResource, ok := con.proxy.WatchedResources[stype]; ok {
   133  					pxc.ConfigStatus = debugSyncStatus(watchedResource)
   134  				} else if isZtunnel(con) {
   135  					pxc.ConfigStatus = status.ConfigStatus_UNKNOWN
   136  				} else {
   137  					pxc.ConfigStatus = status.ConfigStatus_NOT_SENT
   138  				}
   139  
   140  				pxc.TypeUrl = stype
   141  
   142  				xdsConfigs = append(xdsConfigs, pxc)
   143  			}
   144  			clientConfig := &status.ClientConfig{
   145  				Node: &core.Node{
   146  					Id: con.proxy.ID,
   147  					Metadata: model.NodeMetadata{
   148  						ClusterID:    con.proxy.Metadata.ClusterID,
   149  						Namespace:    con.proxy.Metadata.Namespace,
   150  						IstioVersion: con.proxy.Metadata.IstioVersion,
   151  					}.ToStruct(),
   152  				},
   153  				GenericXdsConfigs: xdsConfigs,
   154  			}
   155  			res = append(res, &discovery.Resource{
   156  				Name:     clientConfig.Node.Id,
   157  				Resource: protoconv.MessageToAny(clientConfig),
   158  			})
   159  		}
   160  		con.proxy.RUnlock()
   161  	}
   162  
   163  	return res
   164  }
   165  
   166  func debugSyncStatus(wr *model.WatchedResource) status.ConfigStatus {
   167  	if wr.NonceSent == "" {
   168  		return status.ConfigStatus_NOT_SENT
   169  	}
   170  	if wr.NonceAcked == wr.NonceSent {
   171  		return status.ConfigStatus_SYNCED
   172  	}
   173  	return status.ConfigStatus_STALE
   174  }
   175  
   176  func (sg *StatusGen) debugConfigDump(proxyID string) (model.Resources, error) {
   177  	conn := sg.Server.getProxyConnection(proxyID)
   178  	if conn == nil {
   179  		// This is "like" a 404.  The error is the client's.  However, this endpoint
   180  		// only tracks a single "shard" of connections.  The client may try another instance.
   181  		return nil, fmt.Errorf("config dump could not find connection for proxyID %q", proxyID)
   182  	}
   183  
   184  	dump, err := sg.Server.connectionConfigDump(conn, false)
   185  	if err != nil {
   186  		return nil, err
   187  	}
   188  
   189  	return model.AnyToUnnamedResources(dump.Configs), nil
   190  }