istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/xds/xdsgen.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  	"encoding/json"
    19  	"strconv"
    20  	"strings"
    21  	"time"
    22  
    23  	core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
    24  	discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
    25  
    26  	"istio.io/istio/pilot/pkg/model"
    27  	"istio.io/istio/pilot/pkg/networking/util"
    28  	v3 "istio.io/istio/pilot/pkg/xds/v3"
    29  	"istio.io/istio/pkg/env"
    30  	"istio.io/istio/pkg/lazy"
    31  	istioversion "istio.io/istio/pkg/version"
    32  	"istio.io/istio/pkg/xds"
    33  )
    34  
    35  // IstioControlPlaneInstance defines the format Istio uses for when creating Envoy config.core.v3.ControlPlane.identifier
    36  type IstioControlPlaneInstance struct {
    37  	// The Istio component type (e.g. "istiod")
    38  	Component string
    39  	// The ID of the component instance
    40  	ID string
    41  	// The Istio version
    42  	Info istioversion.BuildInfo
    43  }
    44  
    45  // Evaluate the controlPlane lazily in order to allow "POD_NAME" env var setting after running the process.
    46  var controlPlane = lazy.New(func() (*core.ControlPlane, error) {
    47  	// The Pod Name (instance identity) is in PilotArgs, but not reachable globally nor from DiscoveryServer
    48  	podName := env.Register("POD_NAME", "", "").Get()
    49  	byVersion, err := json.Marshal(IstioControlPlaneInstance{
    50  		Component: "istiod",
    51  		ID:        podName,
    52  		Info:      istioversion.Info,
    53  	})
    54  	if err != nil {
    55  		log.Warnf("XDS: Could not serialize control plane id: %v", err)
    56  	}
    57  	return &core.ControlPlane{Identifier: string(byVersion)}, nil
    58  })
    59  
    60  // ControlPlane identifies the instance and Istio version.
    61  func ControlPlane() *core.ControlPlane {
    62  	// Error will never happen because the getter of lazy does not return error.
    63  	cp, _ := controlPlane.Get()
    64  	return cp
    65  }
    66  
    67  func (s *DiscoveryServer) findGenerator(typeURL string, con *Connection) model.XdsResourceGenerator {
    68  	if g, f := s.Generators[con.proxy.Metadata.Generator+"/"+typeURL]; f {
    69  		return g
    70  	}
    71  	if g, f := s.Generators[string(con.proxy.Type)+"/"+typeURL]; f {
    72  		return g
    73  	}
    74  
    75  	if g, f := s.Generators[typeURL]; f {
    76  		return g
    77  	}
    78  
    79  	// XdsResourceGenerator is the default generator for this connection. We want to allow
    80  	// some types to use custom generators - for example EDS.
    81  	g := con.proxy.XdsResourceGenerator
    82  	if g == nil {
    83  		if strings.HasPrefix(typeURL, TypeDebugPrefix) {
    84  			g = s.Generators["event"]
    85  		} else {
    86  			// TODO move this to just directly using the resource TypeUrl
    87  			g = s.Generators["api"] // default to "MCP" generators - any type supported by store
    88  		}
    89  	}
    90  	return g
    91  }
    92  
    93  // Push an XDS resource for the given connection. Configuration will be generated
    94  // based on the passed in generator. Based on the updates field, generators may
    95  // choose to send partial or even no response if there are no changes.
    96  func (s *DiscoveryServer) pushXds(con *Connection, w *model.WatchedResource, req *model.PushRequest) error {
    97  	if w == nil {
    98  		return nil
    99  	}
   100  	gen := s.findGenerator(w.TypeUrl, con)
   101  	if gen == nil {
   102  		return nil
   103  	}
   104  
   105  	t0 := time.Now()
   106  
   107  	// If delta is set, client is requesting new resources or removing old ones. We should just generate the
   108  	// new resources it needs, rather than the entire set of known resources.
   109  	// Note: we do not need to account for unsubscribed resources as these are handled by parent removal;
   110  	// See https://www.envoyproxy.io/docs/envoy/latest/api-docs/xds_protocol#deleting-resources.
   111  	// This means if there are only removals, we will not respond.
   112  	var logFiltered string
   113  	if !req.Delta.IsEmpty() && !con.proxy.IsProxylessGrpc() {
   114  		logFiltered = " filtered:" + strconv.Itoa(len(w.ResourceNames)-len(req.Delta.Subscribed))
   115  		w = &model.WatchedResource{
   116  			TypeUrl:       w.TypeUrl,
   117  			ResourceNames: req.Delta.Subscribed.UnsortedList(),
   118  		}
   119  	}
   120  	res, logdata, err := gen.Generate(con.proxy, w, req)
   121  	info := ""
   122  	if len(logdata.AdditionalInfo) > 0 {
   123  		info = " " + logdata.AdditionalInfo
   124  	}
   125  	if len(logFiltered) > 0 {
   126  		info += logFiltered
   127  	}
   128  	if err != nil || res == nil {
   129  		// If we have nothing to send, report that we got an ACK for this version.
   130  		if s.StatusReporter != nil {
   131  			s.StatusReporter.RegisterEvent(con.ID(), w.TypeUrl, req.Push.LedgerVersion)
   132  		}
   133  		if log.DebugEnabled() {
   134  			log.Debugf("%s: SKIP%s for node:%s%s", v3.GetShortType(w.TypeUrl), req.PushReason(), con.proxy.ID, info)
   135  		}
   136  
   137  		return err
   138  	}
   139  	defer func() { recordPushTime(w.TypeUrl, time.Since(t0)) }()
   140  
   141  	resp := &discovery.DiscoveryResponse{
   142  		ControlPlane: ControlPlane(),
   143  		TypeUrl:      w.TypeUrl,
   144  		// TODO: send different version for incremental eds
   145  		VersionInfo: req.Push.PushVersion,
   146  		Nonce:       nonce(req.Push.LedgerVersion),
   147  		Resources:   xds.ResourcesToAny(res),
   148  	}
   149  
   150  	configSize := ResourceSize(res)
   151  	configSizeBytes.With(typeTag.Value(w.TypeUrl)).Record(float64(configSize))
   152  
   153  	ptype := "PUSH"
   154  	if logdata.Incremental {
   155  		ptype = "PUSH INC"
   156  	}
   157  
   158  	if err := xds.Send(con, resp); err != nil {
   159  		if recordSendError(w.TypeUrl, err) {
   160  			log.Warnf("%s: Send failure for node:%s resources:%d size:%s%s: %v",
   161  				v3.GetShortType(w.TypeUrl), con.proxy.ID, len(res), util.ByteCount(configSize), info, err)
   162  		}
   163  		return err
   164  	}
   165  
   166  	switch {
   167  	case !req.Full:
   168  		if log.DebugEnabled() {
   169  			log.Debugf("%s: %s%s for node:%s resources:%d size:%s%s",
   170  				v3.GetShortType(w.TypeUrl), ptype, req.PushReason(), con.proxy.ID, len(res), util.ByteCount(configSize), info)
   171  		}
   172  	default:
   173  		debug := ""
   174  		if log.DebugEnabled() {
   175  			// Add additional information to logs when debug mode enabled.
   176  			debug = " nonce:" + resp.Nonce + " version:" + resp.VersionInfo
   177  		}
   178  		log.Infof("%s: %s%s for node:%s resources:%d size:%v%s%s", v3.GetShortType(w.TypeUrl), ptype, req.PushReason(), con.proxy.ID, len(res),
   179  			util.ByteCount(ResourceSize(res)), info, debug)
   180  	}
   181  
   182  	return nil
   183  }
   184  
   185  func ResourceSize(r model.Resources) int {
   186  	// Approximate size by looking at the Any marshaled size. This avoids high cost
   187  	// proto.Size, at the expense of slightly under counting.
   188  	size := 0
   189  	for _, r := range r {
   190  		size += len(r.Resource.Value)
   191  	}
   192  	return size
   193  }