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 }