istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/networking/apigen/apigen.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 apigen 16 17 import ( 18 "strings" 19 20 discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3" 21 22 "istio.io/istio/pilot/pkg/model" 23 "istio.io/istio/pilot/pkg/serviceregistry/provider" 24 "istio.io/istio/pilot/pkg/serviceregistry/serviceentry" 25 "istio.io/istio/pilot/pkg/util/protoconv" 26 "istio.io/istio/pkg/config" 27 "istio.io/istio/pkg/config/schema/gvk" 28 "istio.io/istio/pkg/log" 29 ) 30 31 // APIGenerator supports generation of high-level API resources, similar with the MCP 32 // protocol. This is a replacement for MCP, using XDS (and in future UDPA) as a transport. 33 // Based on lessons from MCP, the protocol allows incremental updates by 34 // default, using the same mechanism that EDS is using, i.e. sending only changed resources 35 // in a push. Incremental deletes are sent as a resource with empty body. 36 // 37 // Example: networking.istio.io/v1alpha3/VirtualService 38 // 39 // TODO: we can also add a special marker in the header) 40 type APIGenerator struct { 41 // ConfigStore interface for listing istio api resources. 42 store model.ConfigStore 43 } 44 45 func NewGenerator(store model.ConfigStore) *APIGenerator { 46 return &APIGenerator{ 47 store: store, 48 } 49 } 50 51 // TODO: take 'updates' into account, don't send pushes for resources that haven't changed 52 // TODO: support WorkloadEntry - to generate endpoints (equivalent with EDS) 53 // TODO: based on lessons from MCP, we want to send 'chunked' responses, like apiserver does. 54 // A first attempt added a 'sync' record at the end. Based on feedback and common use, a 55 // different approach can be used - for large responses, we can mark the last one as 'hasMore' 56 // by adding a field to the envelope. 57 58 // Generate implements the generate method for high level APIs, like Istio config types. 59 // This provides similar functionality with MCP and :8080/debug/configz. 60 // 61 // Names are based on the current resource naming in istiod stores. 62 func (g *APIGenerator) Generate(proxy *model.Proxy, w *model.WatchedResource, req *model.PushRequest) (model.Resources, model.XdsLogDetails, error) { 63 resp := model.Resources{} 64 65 // Note: this is the style used by MCP and its config. Pilot is using 'Group/Version/Kind' as the 66 // key, which is similar. 67 // 68 // The actual type in the Any should be a real proto - which is based on the generated package name. 69 // For example: type is for Any is 'type.googlepis.com/istio.networking.v1alpha3.EnvoyFilter 70 // We use: networking.istio.io/v1alpha3/EnvoyFilter 71 kind := strings.SplitN(w.TypeUrl, "/", 3) 72 if len(kind) != 3 { 73 log.Warnf("ADS: Unknown watched resources %s", w.TypeUrl) 74 // Still return an empty response - to not break waiting code. It is fine to not know about some resource. 75 return resp, model.DefaultXdsLogDetails, nil 76 } 77 // TODO: extra validation may be needed - at least logging that a resource 78 // of unknown type was requested. This should not be an error - maybe client asks 79 // for a valid CRD we just don't know about. An empty set indicates we have no such config. 80 rgvk := config.GroupVersionKind{ 81 Group: kind[0], 82 Version: kind[1], 83 Kind: kind[2], 84 } 85 if w.TypeUrl == gvk.MeshConfig.String() { 86 resp = append(resp, &discovery.Resource{ 87 Resource: protoconv.MessageToAny(req.Push.Mesh), 88 }) 89 return resp, model.DefaultXdsLogDetails, nil 90 } 91 92 cfg := g.store.List(rgvk, "") 93 for _, c := range cfg { 94 // Right now model.Config is not a proto - until we change it, mcp.Resource. 95 // This also helps migrating MCP users. 96 97 b, err := config.PilotConfigToResource(&c) 98 if err != nil { 99 log.WithLabels("resource", c.NamespacedName()).Warnf("resource error: %v", err) 100 continue 101 } 102 resp = append(resp, &discovery.Resource{ 103 Name: c.Namespace + "/" + c.Name, 104 Resource: protoconv.MessageToAny(b), 105 }) 106 } 107 108 // TODO: MeshConfig, current dynamic ProxyConfig (for this proxy), Networks 109 110 if w.TypeUrl == gvk.ServiceEntry.String() { 111 // Include 'synthetic' SE - but without the endpoints. Used to generate CDS, LDS. 112 // EDS is pass-through. 113 svcs := proxy.SidecarScope.Services() 114 for _, s := range svcs { 115 // Ignore services that are result of conversion from ServiceEntry. 116 if s.Attributes.ServiceRegistry == provider.External { 117 continue 118 } 119 c := serviceentry.ServiceToServiceEntry(s, proxy) 120 b, err := config.PilotConfigToResource(c) 121 if err != nil { 122 log.WithLabels("resource", c.NamespacedName()).Warnf("resource error: %v", err) 123 continue 124 } 125 resp = append(resp, &discovery.Resource{ 126 Name: c.Namespace + "/" + c.Name, 127 Resource: protoconv.MessageToAny(b), 128 }) 129 } 130 } 131 132 return resp, model.DefaultXdsLogDetails, nil 133 }