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 }