github.com/cilium/cilium@v1.16.2/pkg/clustermesh/endpointslicesync/service_informer.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package endpointslicesync
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"maps"
    10  	"strings"
    11  	"sync/atomic"
    12  
    13  	"github.com/sirupsen/logrus"
    14  	v1 "k8s.io/api/core/v1"
    15  	"k8s.io/apimachinery/pkg/api/errors"
    16  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    17  	"k8s.io/apimachinery/pkg/labels"
    18  	"k8s.io/apimachinery/pkg/types"
    19  	"k8s.io/apimachinery/pkg/util/intstr"
    20  	listersv1 "k8s.io/client-go/listers/core/v1"
    21  	"k8s.io/client-go/tools/cache"
    22  	mcsapiv1alpha1 "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
    23  
    24  	"github.com/cilium/cilium/pkg/annotation"
    25  	"github.com/cilium/cilium/pkg/clustermesh/common"
    26  	"github.com/cilium/cilium/pkg/k8s"
    27  	"github.com/cilium/cilium/pkg/k8s/resource"
    28  	slim_corev1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/api/core/v1"
    29  	"github.com/cilium/cilium/pkg/service/store"
    30  )
    31  
    32  const (
    33  	meshServiceNameLabel    = "mesh.cilium.io/service-name"
    34  	meshServiceClusterLabel = "mesh.cilium.io/service-cluster"
    35  )
    36  
    37  // meshServiceInformer uses the ClusterServices to pass fake services to the controllers
    38  // based on the services in the mesh.
    39  // Those services will be named with this form `$svcName-$clusterName` and contains
    40  // a mcsapi source cluster label so that the underlying endpointslice will
    41  // be (almost) correct. We also have a meshClient that overrides some endpointslice
    42  // methods to fix back the service label name.
    43  // The selector labels are always `mesh.cilium.io/service-key` and `mesh.cilium.io/service-cluster`
    44  // so that the meshPodInformer can use that for his labels.
    45  type meshServiceInformer struct {
    46  	dummyInformer
    47  
    48  	logger             logrus.FieldLogger
    49  	globalServiceCache *common.GlobalServiceCache
    50  	services           resource.Resource[*slim_corev1.Service]
    51  	serviceStore       resource.Store[*slim_corev1.Service]
    52  	meshNodeInformer   *meshNodeInformer
    53  
    54  	servicesSynced atomic.Bool
    55  	handler        cache.ResourceEventHandler
    56  }
    57  
    58  func newNotFoundError(message string) *errors.StatusError {
    59  	return &errors.StatusError{ErrStatus: metav1.Status{
    60  		Status:  metav1.StatusFailure,
    61  		Reason:  metav1.StatusReasonNotFound,
    62  		Message: message,
    63  	}}
    64  }
    65  
    66  func doesServiceSyncEndpointSlice(svc *slim_corev1.Service) bool {
    67  	value, ok := annotation.Get(svc, annotation.GlobalService)
    68  	if !ok || strings.ToLower(value) != "true" {
    69  		return false
    70  	}
    71  
    72  	value, ok = annotation.Get(svc, annotation.GlobalServiceSyncEndpointSlices)
    73  	if !ok {
    74  		// If the service is headless we sync the EndpointSlice by default
    75  		return svc.Spec.ClusterIP == v1.ClusterIPNone
    76  	}
    77  	return strings.ToLower(value) == "true"
    78  }
    79  
    80  func (i *meshServiceInformer) refreshAllCluster(svc *slim_corev1.Service) error {
    81  	if i.handler == nil {
    82  		// We don't really need to return an error here as this means that the EndpointSlice controller
    83  		// has not started yet and the controller will resync the initial state anyway
    84  		return nil
    85  	}
    86  
    87  	if globalSvc := i.globalServiceCache.GetGlobalService(types.NamespacedName{Name: svc.Name, Namespace: svc.Namespace}); globalSvc != nil {
    88  		for _, clusterSvc := range globalSvc.ClusterServices {
    89  			// It doesn't matter which event we trigger as the controller ends up always
    90  			// queuing the update the same way regardless of the event.
    91  			if svc, err := i.clusterSvcToSvc(clusterSvc, true); err == nil {
    92  				i.handler.OnAdd(svc, false)
    93  			}
    94  		}
    95  	}
    96  
    97  	return nil
    98  }
    99  
   100  func newMeshServiceInformer(
   101  	logger logrus.FieldLogger,
   102  	globalServiceCache *common.GlobalServiceCache,
   103  	services resource.Resource[*slim_corev1.Service],
   104  	meshNodeInformer *meshNodeInformer,
   105  ) *meshServiceInformer {
   106  	return &meshServiceInformer{
   107  		dummyInformer:      dummyInformer{name: "meshServiceInformer", logger: logger},
   108  		logger:             logger,
   109  		globalServiceCache: globalServiceCache,
   110  		services:           services,
   111  		meshNodeInformer:   meshNodeInformer,
   112  	}
   113  }
   114  
   115  // toKubeServicePort use the clusterSvc to get a list of ServicePort to build
   116  // the kubernetes (non slim) Service. Note that we cannot use the slim Service to get this
   117  // as the slim Service trims the TargetPort which we needs inside the EndpointSliceReconciler
   118  func toKubeServicePort(clusterSvc *store.ClusterService) []v1.ServicePort {
   119  	// Merge all the port config into one to get all the possible ports
   120  	globalPortConfig := store.PortConfiguration{}
   121  	for _, portConfig := range clusterSvc.Backends {
   122  		for name, l4Addr := range portConfig {
   123  			globalPortConfig[name] = l4Addr
   124  		}
   125  	}
   126  
   127  	// Get the ServicePort from the PortConfig
   128  	regularServicePorts := make([]v1.ServicePort, 0, len(globalPortConfig))
   129  	for name, l4Addr := range globalPortConfig {
   130  		regularServicePorts = append(regularServicePorts, v1.ServicePort{
   131  			Name:       name,
   132  			Protocol:   v1.Protocol(l4Addr.Protocol),
   133  			TargetPort: intstr.FromInt(int(l4Addr.Port)),
   134  		})
   135  	}
   136  	return regularServicePorts
   137  }
   138  
   139  // toKubeIpFamilies convert the ipFamilies from the Cilium slim Service type
   140  // to the regular Kubernetes Service type
   141  func toKubeIpFamilies(ipFamilies []slim_corev1.IPFamily) []v1.IPFamily {
   142  	regularIpFamilies := make([]v1.IPFamily, len(ipFamilies))
   143  	for i, ipFamily := range ipFamilies {
   144  		regularIpFamilies[i] = v1.IPFamily(ipFamily)
   145  	}
   146  	return regularIpFamilies
   147  }
   148  
   149  func (i *meshServiceInformer) clusterSvcToSvc(clusterSvc *store.ClusterService, force bool) (*v1.Service, error) {
   150  	if i.serviceStore == nil {
   151  		return nil, fmt.Errorf("service informer not started yet")
   152  	}
   153  
   154  	svc, exists, err := i.serviceStore.GetByKey(resource.Key{Name: clusterSvc.Name, Namespace: clusterSvc.Namespace})
   155  	if err != nil {
   156  		return nil, err
   157  	}
   158  
   159  	if !exists {
   160  		return nil, newNotFoundError(fmt.Sprintf("service '%s' not found", clusterSvc.NamespaceServiceName()))
   161  	}
   162  
   163  	if !force && !doesServiceSyncEndpointSlice(svc) {
   164  		return nil, newNotFoundError(fmt.Sprintf("service '%s' does not have sync endpoint slice annotation", clusterSvc.NamespaceServiceName()))
   165  	}
   166  
   167  	labels := maps.Clone(svc.Labels)
   168  	if labels == nil {
   169  		labels = map[string]string{}
   170  	}
   171  	maps.Copy(labels, map[string]string{
   172  		meshRealServiceNameLabel:          clusterSvc.Name,
   173  		mcsapiv1alpha1.LabelSourceCluster: clusterSvc.Cluster,
   174  	})
   175  
   176  	return &v1.Service{
   177  		ObjectMeta: metav1.ObjectMeta{
   178  			Name:      clusterSvc.Name + "-" + clusterSvc.Cluster,
   179  			UID:       svc.UID,
   180  			Namespace: clusterSvc.Namespace,
   181  			Labels:    labels,
   182  		},
   183  		Spec: v1.ServiceSpec{
   184  			Ports: toKubeServicePort(clusterSvc),
   185  			Selector: map[string]string{
   186  				meshServiceNameLabel:    clusterSvc.Name,
   187  				meshServiceClusterLabel: clusterSvc.Cluster,
   188  			},
   189  			ClusterIP:  svc.Spec.ClusterIP,
   190  			ClusterIPs: svc.Spec.ClusterIPs,
   191  			Type:       v1.ServiceType(svc.Spec.Type),
   192  			IPFamilies: toKubeIpFamilies(svc.Spec.IPFamilies),
   193  		},
   194  	}, nil
   195  }
   196  
   197  type meshServiceLister struct {
   198  	informer  *meshServiceInformer
   199  	namespace string
   200  }
   201  
   202  // List returns the matrix of all the local services and all the remote clusters.
   203  // This is not similar to what does the Get method. For instance, List may returns
   204  // services that could not be found on a call to the Get method.
   205  // By doing that we can ensure that the controller reconciliation is called
   206  // on every possible remote services especially the one that are deleted while
   207  // we still have some EndpointSlices locally (which won't be cleared by the
   208  // OwnerReference mechanism since our actual local service is not deleted).
   209  func (l meshServiceLister) List(selector labels.Selector) ([]*v1.Service, error) {
   210  	reqs, _ := selector.Requirements()
   211  	if !selector.Empty() {
   212  		return nil, fmt.Errorf("meshServiceInformer only supports listing everything as requirements: %s", reqs)
   213  	}
   214  
   215  	originalSvcs, err := l.informer.serviceStore.ByIndex(k8s.NamespaceIndex, l.namespace)
   216  	if err != nil {
   217  		return nil, err
   218  	}
   219  
   220  	clusters := l.informer.meshNodeInformer.ListClusters()
   221  	var svcs []*v1.Service
   222  	for _, svc := range originalSvcs {
   223  		for _, cluster := range clusters {
   224  			dummyClusterSvc := &store.ClusterService{
   225  				Cluster:   cluster,
   226  				Name:      svc.Name,
   227  				Namespace: l.namespace,
   228  			}
   229  			if svc, err := l.informer.clusterSvcToSvc(dummyClusterSvc, false); err == nil {
   230  				svcs = append(svcs, svc)
   231  			}
   232  		}
   233  	}
   234  
   235  	return svcs, nil
   236  }
   237  
   238  // Get attempts to retrieve a *store.ClusterService object by separating the
   239  // service name in the full service name and then convert it to a regular
   240  // Kubernetes Service.
   241  func (l meshServiceLister) Get(name string) (*v1.Service, error) {
   242  	// TODO: We could try to use an illegal character to separate the service name and
   243  	// the cluster name
   244  	posClusterName := len(name)
   245  	for {
   246  		posClusterName = strings.LastIndex(name[:posClusterName], "-")
   247  		if posClusterName == -1 {
   248  			break
   249  		}
   250  
   251  		svcName := name[:posClusterName]
   252  		clusterName := name[posClusterName+1:]
   253  		clusterSvc := l.informer.globalServiceCache.GetService(types.NamespacedName{Name: svcName, Namespace: l.namespace}, clusterName)
   254  
   255  		if clusterSvc == nil {
   256  			continue
   257  		}
   258  		return l.informer.clusterSvcToSvc(clusterSvc, false)
   259  	}
   260  
   261  	return nil, newNotFoundError(fmt.Sprintf("cannot find cluster service with name '%s'", name))
   262  }
   263  
   264  func (i *meshServiceInformer) AddEventHandler(handler cache.ResourceEventHandler) (cache.ResourceEventHandlerRegistration, error) {
   265  	i.handler = handler
   266  	return i, nil
   267  }
   268  
   269  func (i *meshServiceInformer) HasSynced() bool {
   270  	return i.servicesSynced.Load()
   271  }
   272  
   273  func (i *meshServiceInformer) Start(ctx context.Context) error {
   274  	var err error
   275  	if i.serviceStore, err = i.services.Store(ctx); err != nil {
   276  		return err
   277  	}
   278  
   279  	go func() {
   280  		for event := range i.services.Events(ctx) {
   281  			var err error
   282  			switch event.Kind {
   283  			case resource.Sync:
   284  				i.logger.Debug("Local services are synced")
   285  				i.servicesSynced.Store(true)
   286  			case resource.Upsert:
   287  				err = i.refreshAllCluster(event.Object)
   288  			case resource.Delete:
   289  				err = i.refreshAllCluster(event.Object)
   290  			}
   291  			event.Done(err)
   292  		}
   293  	}()
   294  	return nil
   295  }
   296  
   297  func (i *meshServiceInformer) Services(namespace string) listersv1.ServiceNamespaceLister {
   298  	return &meshServiceLister{informer: i, namespace: namespace}
   299  }
   300  func (i *meshServiceInformer) Informer() cache.SharedIndexInformer {
   301  	return i
   302  }
   303  func (i *meshServiceInformer) Lister() listersv1.ServiceLister {
   304  	return i
   305  }
   306  
   307  func (i *meshServiceInformer) List(selector labels.Selector) (ret []*v1.Service, err error) {
   308  	i.logger.Error("called not implemented function meshServiceInformer.List")
   309  	return nil, nil
   310  }