github.com/cilium/cilium@v1.16.2/operator/pkg/model/ingestion/gamma.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package ingestion
     5  
     6  import (
     7  	"fmt"
     8  
     9  	corev1 "k8s.io/api/core/v1"
    10  	"k8s.io/apimachinery/pkg/types"
    11  	gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
    12  	gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
    13  	"sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
    14  
    15  	"github.com/cilium/cilium/operator/pkg/gateway-api/helpers"
    16  	"github.com/cilium/cilium/operator/pkg/model"
    17  )
    18  
    19  // Input is the input for GatewayAPI.
    20  type GammaInput struct {
    21  	HTTPRoutes      []gatewayv1.HTTPRoute
    22  	ReferenceGrants []gatewayv1beta1.ReferenceGrant
    23  	Services        []corev1.Service
    24  }
    25  
    26  // GammaHTTPRoutes takes a GammaInput and gives back the associated HTTP Listeners
    27  // It does not support TLS Routes because GAMMA is only for cleartext config -
    28  // it is assumed that any TLS will be performed transparently by the underlying
    29  // implementation in the spec.
    30  func GammaHTTPRoutes(input GammaInput) []model.HTTPListener {
    31  	// GAMMA processing process:
    32  	// Process HTTPRoutes
    33  	var resHTTP []model.HTTPListener
    34  
    35  	// Search algorithm:
    36  	// Loop through all HTTPRoutes in input, validate that each is parented by a
    37  	//   svc that's also in input, in the same namespace, etc.
    38  	// Add each parentRef service to some tracker to know if we need to create
    39  	//   a new HTTPListener or not.
    40  	// Add the rules from the HTTPRoute to the relevant HTTPListener.
    41  	//   unresolved: How to order these rules?
    42  	// ReferenceGrants are only relevant for backends, I think, because parents
    43  	//  _can_ be across namespace boundaries, but that makes them a consumer
    44  	// route, not a producer one.
    45  
    46  	// Set of services that will be parents for these HTTPRoutes
    47  	parentServices := make(map[types.NamespacedName]model.FullyQualifiedResource)
    48  
    49  	for _, hr := range input.HTTPRoutes {
    50  		// First, we find which parentRefs are Services, so that we can add the
    51  		// route rules to Listeners for those Services.
    52  		var gammaParents []gatewayv1.ParentReference
    53  		for _, parent := range hr.Spec.ParentRefs {
    54  			if helpers.IsGammaService(parent) {
    55  				gammaParents = append(gammaParents, parent)
    56  			}
    57  		}
    58  
    59  		hrSource := model.FullyQualifiedResource{
    60  			Name:      hr.GetName(),
    61  			Namespace: hr.GetNamespace(),
    62  			Group:     gatewayv1.GroupVersion.Group,
    63  			Kind:      "HTTPRoute",
    64  			Version:   gatewayv1.GroupVersion.Version,
    65  			UID:       string(hr.GetUID()),
    66  		}
    67  
    68  		// We shouldn't be able to do this, because the reconciliation should
    69  		// screen out HTTPRoutes with zero GAMMA parents.
    70  		if len(gammaParents) == 0 {
    71  			continue
    72  		}
    73  
    74  		for _, gp := range gammaParents {
    75  
    76  			if gp.Name == "" {
    77  				continue
    78  			}
    79  
    80  			parentName := types.NamespacedName{
    81  				Name: string(gp.Name),
    82  			}
    83  
    84  			if gp.Namespace != nil {
    85  				parentName.Namespace = string(*gp.Namespace)
    86  			}
    87  
    88  			parentSvc, err := getMatchingService(parentName.Name, parentName.Namespace, hr.GetNamespace(), input.Services)
    89  			if err != nil {
    90  				log.Warnf("Can't find parent Service %s/%s in input. This is a bug, please report it to the developers.", parentName.Namespace, parentName.Name)
    91  				continue
    92  			}
    93  
    94  			if parentSvc.GetName() == "" {
    95  				// skip processing this parent because it's not in the input.
    96  				// This situation should not arise - there should be multiple
    97  				// layers of protection.
    98  				log.Warn("Can't find any parent Service in input. This is a bug, please report it to the developers.")
    99  				continue
   100  			}
   101  
   102  			// Record the service as relevant if it's not already
   103  			if _, ok := parentServices[parentName]; !ok {
   104  				parentServices[parentName] = model.FullyQualifiedResource{
   105  					Name:      parentSvc.GetName(),
   106  					Namespace: parentSvc.GetNamespace(),
   107  					Group:     corev1.GroupName,
   108  					Kind:      "Service",
   109  					Version:   corev1.SchemeGroupVersion.Version,
   110  					UID:       string(parentSvc.GetUID()),
   111  				}
   112  			}
   113  
   114  			// Pick which ports from the Service are relevant.
   115  			var relevantPorts []uint32
   116  			// If there's a Port set in the parentRef, then that's the only relevant port.
   117  			if gp.Port != nil && *gp.Port != 0 {
   118  				relevantPorts = append(relevantPorts, uint32(*gp.Port))
   119  			} else {
   120  				// Otherwise, we find ones where appProtocol is http
   121  				for _, port := range parentSvc.Spec.Ports {
   122  					if port.Protocol == "" || port.Protocol == "TCP" {
   123  						// This is a little suspect, but we should only be using ones where AppProtocol is http
   124  						// _apparently_
   125  						if (port.AppProtocol == nil) || (port.AppProtocol != nil && *port.AppProtocol != "http") {
   126  							continue
   127  						}
   128  					}
   129  
   130  					relevantPorts = append(relevantPorts, uint32(port.Port))
   131  				}
   132  			}
   133  
   134  			// We need a Listener per port on the Service that we are handling.
   135  			for _, portVal := range relevantPorts {
   136  				res := model.HTTPListener{}
   137  				// Record the parent Service as the source of the Listener.
   138  				res.Sources = append(res.Sources, parentServices[parentName])
   139  				// Record the HTTPRoute as another Source, so that we can ensure that the CEC will get cleaned up
   140  				// when the HTTPRoute does
   141  				res.Sources = append(res.Sources, hrSource)
   142  				res.Name = fmt.Sprintf("%s-%s-%d", parentSvc.GetNamespace(), parentSvc.GetName(), portVal)
   143  				res.Port = portVal
   144  				// GAMMA spec _explicitly_ says that we must not filter by hostname, only address and port
   145  				res.Hostname = "*"
   146  
   147  				res.Service = &model.Service{
   148  					Type: string(corev1.ServiceTypeClusterIP),
   149  				}
   150  
   151  				res.Routes = append(res.Routes, extractRoutes(int32(portVal), []string{res.Hostname}, hr, input.Services, []v1alpha1.ServiceImport{}, input.ReferenceGrants)...)
   152  				resHTTP = append(resHTTP, res)
   153  			}
   154  
   155  		}
   156  	}
   157  	return resHTTP
   158  }
   159  
   160  func getMatchingService(name string, parentNamespace string, hrNamespace string, services []corev1.Service) (corev1.Service, error) {
   161  	for _, svc := range services {
   162  		if svc.GetName() == name {
   163  			if (parentNamespace == svc.GetNamespace()) || (parentNamespace == "" && hrNamespace == svc.GetNamespace()) {
   164  				return svc, nil
   165  			}
   166  		}
   167  	}
   168  
   169  	return corev1.Service{}, fmt.Errorf("service not found in input")
   170  }