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 }