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 }