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  }