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  }