...

Source file src/github.com/emissary-ingress/emissary/v3/pkg/ambex/transforms.go

Documentation: github.com/emissary-ingress/emissary/v3/pkg/ambex

     1  package ambex
     2  
     3  import (
     4  	// standard library
     5  	"context"
     6  	"fmt"
     7  
     8  	// third-party libraries
     9  	"google.golang.org/protobuf/proto"
    10  	"google.golang.org/protobuf/types/known/anypb"
    11  
    12  	// envoy api v3
    13  	v3cluster "github.com/emissary-ingress/emissary/v3/pkg/api/envoy/config/cluster/v3"
    14  	v3core "github.com/emissary-ingress/emissary/v3/pkg/api/envoy/config/core/v3"
    15  	v3endpoint "github.com/emissary-ingress/emissary/v3/pkg/api/envoy/config/endpoint/v3"
    16  	v3listener "github.com/emissary-ingress/emissary/v3/pkg/api/envoy/config/listener/v3"
    17  	v3route "github.com/emissary-ingress/emissary/v3/pkg/api/envoy/config/route/v3"
    18  	v3httpman "github.com/emissary-ingress/emissary/v3/pkg/api/envoy/extensions/filters/network/http_connection_manager/v3"
    19  
    20  	// envoy control plane
    21  	ecp_cache_types "github.com/emissary-ingress/emissary/v3/pkg/envoy-control-plane/cache/types"
    22  	ecp_v3_resource "github.com/emissary-ingress/emissary/v3/pkg/envoy-control-plane/resource/v3"
    23  	ecp_wellknown "github.com/emissary-ingress/emissary/v3/pkg/envoy-control-plane/wellknown"
    24  
    25  	// first-party libraries
    26  	"github.com/datawire/dlib/dlog"
    27  )
    28  
    29  // ListenerToRdsListener will take a listener definition and extract any inline RouteConfigurations
    30  // replacing them with a reference to an RDS supplied route configuration. It does not modify the
    31  // supplied listener, any configuration included in the result is copied from the input.
    32  //
    33  // If the input listener does not match the expected form it is simply copied, i.e. it is the
    34  // identity transform for any inputs not matching the expected form.
    35  //
    36  // Example Input (that will get transformed in a non-identity way):
    37  //   - a listener configured with an http connection manager
    38  //   - that specifies an http router
    39  //   - that supplies its RouteConfiguration inline via the route_config field
    40  //
    41  //   {
    42  //     "name": "...",
    43  //     ...,
    44  //     "filter_chains": [
    45  //       {
    46  //         "filter_chain_match": {...},
    47  //         "filters": [
    48  //           {
    49  //             "name": "envoy.filters.network.http_connection_manager",
    50  //             "typed_config": {
    51  //               "@type": "type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager",
    52  //               "http_filters": [...],
    53  //               "route_config": {
    54  //                 "virtual_hosts": [
    55  //                   {
    56  //                     "name": "ambassador-listener-8443-*",
    57  //                     "domains": ["*"],
    58  //                     "routes": [...],
    59  //                   }
    60  //                 ]
    61  //               }
    62  //             }
    63  //           }
    64  //         ]
    65  //       }
    66  //     ]
    67  //   }
    68  //
    69  // Example Output:
    70  //   - a duplicate listener that defines the "rds" field instead of the "route_config" field
    71  //   - and a list of route configurations
    72  //   - with route_config_name supplied in such a way as to correlate the two together
    73  //
    74  //   lnr, routes, err := ListenerToRdsListener(...)
    75  //
    76  //   lnr = {
    77  //     "name": "...",
    78  //     ...,
    79  //     "filter_chains": [
    80  //       {
    81  //         "filter_chain_match": {...},
    82  //         "filters": [
    83  //           {
    84  //             "name": "envoy.filters.network.http_connection_manager",
    85  //             "typed_config": {
    86  //               "@type": "type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager",
    87  //               "http_filters": [...],
    88  //               "rds": {
    89  //                 "config_source": {
    90  //                   "ads": {}
    91  //                 },
    92  //                 "route_config_name": "ambassador-listener-8443-routeconfig-0"
    93  //               }
    94  //             }
    95  //           }
    96  //         ]
    97  //       }
    98  //     ]
    99  //   }
   100  //
   101  //  routes = [
   102  //    {
   103  //      "name": "ambassador-listener-8443-routeconfig-0",
   104  //      "virtual_hosts": [
   105  //        {
   106  //          "name": "ambassador-listener-8443-*",
   107  //          "domains": ["*"],
   108  //          "routes": [...],
   109  //        }
   110  //      ]
   111  //    }
   112  //  ]
   113  
   114  // V3ListenerToRdsListener is the v3 variety of ListnerToRdsListener
   115  func V3ListenerToRdsListener(lnr *v3listener.Listener) (*v3listener.Listener, []*v3route.RouteConfiguration, error) {
   116  	l := proto.Clone(lnr).(*v3listener.Listener)
   117  	var routes []*v3route.RouteConfiguration
   118  	for _, fc := range l.FilterChains {
   119  		for _, f := range fc.Filters {
   120  			if f.Name != ecp_wellknown.HTTPConnectionManager {
   121  				// We only know how to create an rds listener for HttpConnectionManager
   122  				// listeners. We must ignore all other listeners.
   123  				continue
   124  			}
   125  
   126  			// Note that the hcm configuration is stored in a protobuf any, so √the
   127  			// GetHTTPConnectionManager is actually returning an unmarshalled copy.
   128  			hcm := ecp_v3_resource.GetHTTPConnectionManager(f)
   129  			if hcm != nil {
   130  				// RouteSpecifier is a protobuf oneof that corresponds to the rds, route_config, and
   131  				// scoped_routes fields. Only one of those may be set at a time.
   132  				rs, ok := hcm.RouteSpecifier.(*v3httpman.HttpConnectionManager_RouteConfig)
   133  				if ok {
   134  					rc := rs.RouteConfig
   135  					if rc.Name == "" {
   136  						// Generate a unique name for the RouteConfiguration that we can use to
   137  						// correlate the listener to the RDS record. We use the listener name plus
   138  						// an index because there can be more than one route configuration
   139  						// associated with a given listener.
   140  						rc.Name = fmt.Sprintf("%s-routeconfig-%d", l.Name, len(routes))
   141  					}
   142  					routes = append(routes, rc)
   143  					// Now that we have extracted and named the RouteConfiguration, we change the
   144  					// RouteSpecifier from the inline RouteConfig variation to RDS via ADS. This
   145  					// will cause it to use whatever ADS source is defined in the bootstrap
   146  					// configuration.
   147  					hcm.RouteSpecifier = &v3httpman.HttpConnectionManager_Rds{
   148  						Rds: &v3httpman.Rds{
   149  							ConfigSource: &v3core.ConfigSource{
   150  								ConfigSourceSpecifier: &v3core.ConfigSource_Ads{
   151  									Ads: &v3core.AggregatedConfigSource{},
   152  								},
   153  								ResourceApiVersion: v3core.ApiVersion_V3,
   154  							},
   155  							RouteConfigName: rc.Name,
   156  						},
   157  					}
   158  				}
   159  
   160  				// Because the hcm is a protobuf any, we need to remarshal it, we can't simply
   161  				// expect the above modifications to take effect on our clone of the input. There is
   162  				// also a protobuf oneof that includes the deprecated config and typed_config
   163  				// fields.
   164  				any, err := anypb.New(hcm)
   165  				if err != nil {
   166  					return nil, nil, err
   167  				}
   168  				f.ConfigType = &v3listener.Filter_TypedConfig{TypedConfig: any}
   169  			}
   170  		}
   171  	}
   172  
   173  	return l, routes, nil
   174  }
   175  
   176  // JoinEdsClustersV3 will perform an outer join operation between the eds clusters in the supplied
   177  // clusterlist and the eds endpoint data in the supplied map. It will return a slice of
   178  // ClusterLoadAssignments (cast to []ecp_cache_types.Resource) with endpoint data for all the eds clusters in
   179  // the supplied list. If there is no map entry for a given cluster, an empty ClusterLoadAssignment
   180  // will be synthesized. The result is a set of endpoints that are consistent (by the
   181  // go-control-plane's definition of consistent) with the input clusters.
   182  func JoinEdsClustersV3(ctx context.Context, clusters []ecp_cache_types.Resource, edsEndpoints map[string]*v3endpoint.ClusterLoadAssignment, edsBypass bool) (endpoints []ecp_cache_types.Resource) {
   183  	for _, clu := range clusters {
   184  		c := clu.(*v3cluster.Cluster)
   185  		// Don't mess with non EDS clusters.
   186  		if c.EdsClusterConfig == nil {
   187  			continue
   188  		}
   189  
   190  		// By default, envoy will use the cluster name to lookup ClusterLoadAssignments unless the
   191  		// ServiceName is supplied in the EdsClusterConfig.
   192  		ref := c.EdsClusterConfig.ServiceName
   193  		if ref == "" {
   194  			ref = c.Name
   195  		}
   196  
   197  		// This change was introduced as a stop gap solution to mitigate the 503 issues when certificates are rotated.
   198  		// The issue is CDS gets updated and waits for EDS to send ClusterLoadAssignment.
   199  		// During this wait period calls that are coming through get hit with a 503 since the cluster is in a warming state.
   200  		// The solution is to "hijack" the cluster and insert all the endpoints instead of relying on EDS.
   201  		// Now there will be a discrepancy between envoy/envoy.json and the config envoy.
   202  		if edsBypass {
   203  			c.EdsClusterConfig = nil
   204  			// Type 0 is STATIC
   205  			c.ClusterDiscoveryType = &v3cluster.Cluster_Type{Type: 0}
   206  
   207  			if ep, ok := edsEndpoints[ref]; ok {
   208  				c.LoadAssignment = ep
   209  			} else {
   210  				c.LoadAssignment = &v3endpoint.ClusterLoadAssignment{
   211  					ClusterName: ref,
   212  					Endpoints:   []*v3endpoint.LocalityLbEndpoints{},
   213  				}
   214  			}
   215  		} else {
   216  			var source string
   217  			ep, ok := edsEndpoints[ref]
   218  			if ok {
   219  				source = "found"
   220  			} else {
   221  				ep = &v3endpoint.ClusterLoadAssignment{
   222  					ClusterName: ref,
   223  					Endpoints:   []*v3endpoint.LocalityLbEndpoints{},
   224  				}
   225  				source = "synthesized"
   226  			}
   227  
   228  			dlog.Debugf(ctx, "%s envoy v3 ClusterLoadAssignment for cluster %s: %v", source, c.Name, ep)
   229  			endpoints = append(endpoints, ep)
   230  		}
   231  
   232  	}
   233  
   234  	return
   235  }
   236  

View as plain text