github.com/cilium/cilium@v1.16.2/operator/pkg/model/translation/gateway-api/translator.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package gateway_api 5 6 import ( 7 "fmt" 8 9 corev1 "k8s.io/api/core/v1" 10 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 "k8s.io/apimachinery/pkg/types" 12 gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1" 13 14 "github.com/cilium/cilium/operator/pkg/model" 15 "github.com/cilium/cilium/operator/pkg/model/translation" 16 ciliumv2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" 17 ) 18 19 var _ translation.Translator = (*gatewayAPITranslator)(nil) 20 21 const ( 22 ciliumGatewayPrefix = "cilium-gateway-" 23 // Deprecated: owningGatewayLabel will be removed later in favour of gatewayNameLabel 24 owningGatewayLabel = "io.cilium.gateway/owning-gateway" 25 gatewayNameLabel = "gateway.networking.k8s.io/gateway-name" 26 ) 27 28 type gatewayAPITranslator struct { 29 cecTranslator translation.CECTranslator 30 31 hostNetworkEnabled bool 32 externalTrafficPolicy string 33 } 34 35 func NewTranslator(cecTranslator translation.CECTranslator, hostNetworkEnabled bool, externalTrafficPolicy string) translation.Translator { 36 return &gatewayAPITranslator{ 37 cecTranslator: cecTranslator, 38 hostNetworkEnabled: hostNetworkEnabled, 39 externalTrafficPolicy: externalTrafficPolicy, 40 } 41 } 42 43 func (t *gatewayAPITranslator) Translate(m *model.Model) (*ciliumv2.CiliumEnvoyConfig, *corev1.Service, *corev1.Endpoints, error) { 44 listeners := m.GetListeners() 45 if len(listeners) == 0 || len(listeners[0].GetSources()) == 0 { 46 return nil, nil, nil, fmt.Errorf("model source can't be empty") 47 } 48 49 // source is the main object that is the source of the model.Model 50 var source *model.FullyQualifiedResource 51 // owner is the object that will be the owner of the created CEC 52 // for Gateways, source == owner == the created LB service 53 // for Services (that is, GAMMA), source is the parent Service, and owner 54 // is the HTTPRoute. 55 var owner *model.FullyQualifiedResource 56 57 var ports []uint32 58 for _, l := range listeners { 59 sources := l.GetSources() 60 source = &sources[0] 61 owner = source 62 // If there's more than one source in the listener, then this model is a GAMMA one, 63 // and includes a HTTPRoute source as the second one. 64 if len(sources) > 1 { 65 owner = &sources[1] 66 } 67 68 ports = append(ports, l.GetPort()) 69 } 70 71 if source == nil || source.Name == "" { 72 return nil, nil, nil, fmt.Errorf("model source name can't be empty") 73 } 74 75 // generatedName is the name of the generated objects. 76 // for Gateways, this is "cilium-gateway-<servicename>" 77 // for GAMMA, this is just "<servicename>" 78 generatedName := ciliumGatewayPrefix + source.Name 79 80 // TODO: remove this hack 81 if source.Kind == "Service" { 82 generatedName = source.Name 83 } 84 cec, err := t.cecTranslator.Translate(source.Namespace, generatedName, m) 85 if err != nil { 86 return nil, nil, nil, err 87 } 88 89 var allLabels, allAnnotations map[string]string 90 // Merge all the labels and annotations from the listeners. 91 // Normally, the labels and annotations are the same for all the listeners having same gateway. 92 for _, l := range listeners { 93 allAnnotations = mergeMap(allAnnotations, l.GetAnnotations()) 94 allLabels = mergeMap(allLabels, l.GetLabels()) 95 } 96 97 if err = decorateCEC(cec, owner, allLabels, allAnnotations); err != nil { 98 return nil, nil, nil, err 99 } 100 101 ep := getEndpoints(*source, allLabels, allAnnotations) 102 lbSvc := getService(source, ports, allLabels, allAnnotations, t.externalTrafficPolicy) 103 104 if t.hostNetworkEnabled { 105 lbSvc.Spec.Type = corev1.ServiceTypeClusterIP 106 lbSvc.Spec.ExternalTrafficPolicy = corev1.ServiceExternalTrafficPolicy("") 107 } 108 109 return cec, lbSvc, ep, err 110 } 111 112 func getService(resource *model.FullyQualifiedResource, allPorts []uint32, labels, annotations map[string]string, externalTrafficPolicy string) *corev1.Service { 113 uniquePorts := map[uint32]struct{}{} 114 for _, p := range allPorts { 115 uniquePorts[p] = struct{}{} 116 } 117 118 ports := make([]corev1.ServicePort, 0, len(uniquePorts)) 119 for p := range uniquePorts { 120 ports = append(ports, corev1.ServicePort{ 121 Name: fmt.Sprintf("port-%d", p), 122 Port: int32(p), 123 Protocol: corev1.ProtocolTCP, 124 }) 125 } 126 127 shortenName := model.Shorten(resource.Name) 128 129 return &corev1.Service{ 130 ObjectMeta: metav1.ObjectMeta{ 131 Name: model.Shorten(ciliumGatewayPrefix + resource.Name), 132 Namespace: resource.Namespace, 133 Labels: mergeMap(map[string]string{ 134 owningGatewayLabel: shortenName, 135 gatewayNameLabel: shortenName, 136 }, labels), 137 Annotations: annotations, 138 OwnerReferences: []metav1.OwnerReference{ 139 { 140 APIVersion: gatewayv1beta1.GroupVersion.String(), 141 Kind: resource.Kind, 142 Name: resource.Name, 143 UID: types.UID(resource.UID), 144 Controller: model.AddressOf(true), 145 }, 146 }, 147 }, 148 Spec: corev1.ServiceSpec{ 149 Type: corev1.ServiceTypeLoadBalancer, 150 ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicy(externalTrafficPolicy), 151 Ports: ports, 152 }, 153 } 154 } 155 156 func getEndpoints(resource model.FullyQualifiedResource, labels, annotations map[string]string) *corev1.Endpoints { 157 shortedName := model.Shorten(resource.Name) 158 159 return &corev1.Endpoints{ 160 ObjectMeta: metav1.ObjectMeta{ 161 Name: model.Shorten(ciliumGatewayPrefix + resource.Name), 162 Namespace: resource.Namespace, 163 Labels: mergeMap(map[string]string{ 164 owningGatewayLabel: shortedName, 165 gatewayNameLabel: shortedName, 166 }, labels), 167 Annotations: annotations, 168 OwnerReferences: []metav1.OwnerReference{ 169 { 170 APIVersion: gatewayv1beta1.GroupVersion.String(), 171 Kind: resource.Kind, 172 Name: resource.Name, 173 UID: types.UID(resource.UID), 174 Controller: model.AddressOf(true), 175 }, 176 }, 177 }, 178 Subsets: []corev1.EndpointSubset{ 179 { 180 // This dummy endpoint is required as agent refuses to push service entry 181 // to the lb map when the service has no backends. 182 // Related github issue https://github.com/cilium/cilium/issues/19262 183 Addresses: []corev1.EndpointAddress{{IP: "192.192.192.192"}}, // dummy 184 Ports: []corev1.EndpointPort{{Port: 9999}}, // dummy 185 }, 186 }, 187 } 188 } 189 190 func decorateCEC(cec *ciliumv2.CiliumEnvoyConfig, resource *model.FullyQualifiedResource, labels, annotations map[string]string) error { 191 if cec == nil || resource == nil { 192 return fmt.Errorf("CEC or resource can't be nil") 193 } 194 195 // Set the owner reference to the CEC object. 196 cec.OwnerReferences = []metav1.OwnerReference{ 197 { 198 APIVersion: resource.Group + "/" + resource.Version, 199 Kind: resource.Kind, 200 Name: resource.Name, 201 UID: types.UID(resource.UID), 202 Controller: model.AddressOf(true), 203 }, 204 } 205 206 if cec.Labels == nil { 207 cec.Labels = make(map[string]string) 208 } 209 cec.Labels = mergeMap(cec.Labels, labels) 210 cec.Labels[gatewayNameLabel] = model.Shorten(resource.Name) 211 cec.Annotations = mergeMap(cec.Annotations, annotations) 212 213 return nil 214 } 215 216 func mergeMap(left, right map[string]string) map[string]string { 217 if left == nil { 218 return right 219 } 220 for key, value := range right { 221 left[key] = value 222 } 223 return left 224 }