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

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package endpointslicesync
     5  
     6  import (
     7  	"context"
     8  
     9  	discovery "k8s.io/api/discovery/v1"
    10  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    11  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
    12  	"k8s.io/apimachinery/pkg/watch"
    13  	discoveryv1 "k8s.io/client-go/kubernetes/typed/discovery/v1"
    14  	mcsapiv1alpha1 "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
    15  
    16  	k8sClient "github.com/cilium/cilium/pkg/k8s/client"
    17  	"github.com/cilium/cilium/pkg/k8s/utils"
    18  	"github.com/cilium/cilium/pkg/lock"
    19  )
    20  
    21  const (
    22  	meshRealServiceNameLabel = "mesh.cilium.io/real-service-name"
    23  )
    24  
    25  // meshClient override a few EndpointSlice methods to add/remove "hacks" on EndpointSlice.
    26  // This is necessary because the service informer passed to the controller will create
    27  // fake services with this form `$svcName-$clusterName` whereas the actual Service
    28  // doesn't contain the $clusterName. So this client is used to intercept
    29  // list/watch calls to also make sure that we fake the service name referenced
    30  // by EndpoinSlices and will revert those changes at the create/update level to make
    31  // sure that the correct data is written to the Kubernetes API.
    32  type meshClient struct {
    33  	k8sClient.Clientset
    34  }
    35  
    36  func (c meshClient) DiscoveryV1() discoveryv1.DiscoveryV1Interface {
    37  	return meshClientDiscoveryV1{c.Clientset.DiscoveryV1()}
    38  }
    39  
    40  type meshClientDiscoveryV1 struct {
    41  	discoveryv1.DiscoveryV1Interface
    42  }
    43  
    44  func (c meshClientDiscoveryV1) EndpointSlices(namespace string) discoveryv1.EndpointSliceInterface {
    45  	return meshClientEndpointSlice{c.DiscoveryV1Interface.EndpointSlices(namespace)}
    46  }
    47  
    48  type meshClientEndpointSlice struct {
    49  	discoveryv1.EndpointSliceInterface
    50  }
    51  
    52  func isServiceOwnerReference(ownerReference metav1.OwnerReference) bool {
    53  	return ownerReference.APIVersion == "v1" && ownerReference.Kind == "Service"
    54  }
    55  
    56  // addEndpointSliceMeshHacks fakes that the parent service of the EndpointSlice
    57  // has the cluster name in its name by changing the owner reference and the label
    58  // pointing to the service name. It also make sure that there is a label `mesh.cilium.io/real-service-name`
    59  // to store the real service name before its modification.
    60  func addEndpointSliceMeshHacks(endpointSlice *discovery.EndpointSlice) {
    61  	if endpointSlice == nil ||
    62  		endpointSlice.Labels == nil ||
    63  		endpointSlice.Labels[discovery.LabelManagedBy] != utils.EndpointSliceMeshControllerName {
    64  		return
    65  	}
    66  
    67  	endpointSlice.Labels[meshRealServiceNameLabel] = endpointSlice.Labels[discovery.LabelServiceName]
    68  	endpointSlice.Labels[discovery.LabelServiceName] = endpointSlice.Labels[meshRealServiceNameLabel] + "-" + endpointSlice.Labels[mcsapiv1alpha1.LabelSourceCluster]
    69  
    70  	for i, ownerReference := range endpointSlice.OwnerReferences {
    71  		if !isServiceOwnerReference(ownerReference) || ownerReference.Name != endpointSlice.Labels[meshRealServiceNameLabel] {
    72  			continue
    73  		}
    74  
    75  		endpointSlice.OwnerReferences[i].Name = endpointSlice.Labels[discovery.LabelServiceName]
    76  	}
    77  }
    78  
    79  // removeEndpointSliceMeshHacks revert the change to the parent service made in
    80  // addEndpointSliceMeshHacks.
    81  func removeEndpointSliceMeshHacks(endpointSlice *discovery.EndpointSlice) {
    82  	if endpointSlice == nil ||
    83  		endpointSlice.Labels == nil ||
    84  		endpointSlice.Labels[discovery.LabelManagedBy] != utils.EndpointSliceMeshControllerName {
    85  		return
    86  	}
    87  
    88  	for i, ownerReference := range endpointSlice.OwnerReferences {
    89  		if !isServiceOwnerReference(ownerReference) || ownerReference.Name != endpointSlice.Labels[discovery.LabelServiceName] {
    90  			continue
    91  		}
    92  
    93  		endpointSlice.OwnerReferences[i].Name = endpointSlice.Labels[meshRealServiceNameLabel]
    94  	}
    95  
    96  	endpointSlice.Labels[discovery.LabelServiceName] = endpointSlice.Labels[meshRealServiceNameLabel]
    97  	delete(endpointSlice.Labels, meshRealServiceNameLabel)
    98  }
    99  
   100  func (c meshClientEndpointSlice) Create(ctx context.Context, endpointSlice *discovery.EndpointSlice, opts metav1.CreateOptions) (*discovery.EndpointSlice, error) {
   101  	// Remove the epslice mesh hacks before reaching the api server
   102  	// and add it back for the controller afterwards.
   103  	removeEndpointSliceMeshHacks(endpointSlice)
   104  	endpointSlice, err := c.EndpointSliceInterface.Create(ctx, endpointSlice, opts)
   105  	addEndpointSliceMeshHacks(endpointSlice)
   106  	return endpointSlice, err
   107  }
   108  func (c meshClientEndpointSlice) Update(ctx context.Context, endpointSlice *discovery.EndpointSlice, opts metav1.UpdateOptions) (*discovery.EndpointSlice, error) {
   109  	// Remove the epslice mesh hacks before reaching the api server
   110  	// and add it back for the controller afterwards.
   111  	removeEndpointSliceMeshHacks(endpointSlice)
   112  	endpointSlice, err := c.EndpointSliceInterface.Update(ctx, endpointSlice, opts)
   113  	addEndpointSliceMeshHacks(endpointSlice)
   114  	return endpointSlice, err
   115  }
   116  func (c meshClientEndpointSlice) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error {
   117  	return c.EndpointSliceInterface.Delete(ctx, name, opts)
   118  }
   119  
   120  func (c meshClientEndpointSlice) List(ctx context.Context, opts metav1.ListOptions) (*discovery.EndpointSliceList, error) {
   121  	list, err := c.EndpointSliceInterface.List(ctx, opts)
   122  	if err != nil || list == nil {
   123  		return list, err
   124  	}
   125  
   126  	for _, item := range list.Items {
   127  		addEndpointSliceMeshHacks(&item)
   128  	}
   129  	return list, err
   130  }
   131  func (c meshClientEndpointSlice) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) {
   132  	watchInterface, err := c.EndpointSliceInterface.Watch(ctx, opts)
   133  	return NewMeshEndpointSliceWatcher(watchInterface), err
   134  }
   135  
   136  // Pretty much a copy of watch.Streamwatcher but simplified to have another Streamwatcher
   137  // as a backend while calling addEndpointSliceMeshHacks
   138  type meshEndpointSliceWatcher struct {
   139  	lock.Mutex
   140  	backend watch.Interface
   141  	result  chan watch.Event
   142  }
   143  
   144  func NewMeshEndpointSliceWatcher(backend watch.Interface) *meshEndpointSliceWatcher {
   145  	sw := &meshEndpointSliceWatcher{
   146  		backend: backend,
   147  		result:  make(chan watch.Event),
   148  	}
   149  
   150  	go sw.receive()
   151  	return sw
   152  }
   153  
   154  func (sw *meshEndpointSliceWatcher) ResultChan() <-chan watch.Event {
   155  	return sw.result
   156  }
   157  
   158  func (sw *meshEndpointSliceWatcher) Stop() {
   159  	sw.backend.Stop()
   160  }
   161  
   162  func (sw *meshEndpointSliceWatcher) receive() {
   163  	defer utilruntime.HandleCrash()
   164  	defer close(sw.result)
   165  
   166  	for event := range sw.backend.ResultChan() {
   167  		if event.Object != nil {
   168  			if endpointSlice, ok := event.Object.(*discovery.EndpointSlice); ok {
   169  				addEndpointSliceMeshHacks(endpointSlice)
   170  			}
   171  		}
   172  		sw.result <- event
   173  	}
   174  }