github.com/cilium/cilium@v1.16.2/pkg/clustermesh/endpointslicesync/pod_informer.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package endpointslicesync 5 6 import ( 7 "fmt" 8 9 "github.com/sirupsen/logrus" 10 v1 "k8s.io/api/core/v1" 11 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 "k8s.io/apimachinery/pkg/labels" 13 "k8s.io/apimachinery/pkg/selection" 14 "k8s.io/apimachinery/pkg/types" 15 listersv1 "k8s.io/client-go/listers/core/v1" 16 cache "k8s.io/client-go/tools/cache" 17 18 "github.com/cilium/cilium/pkg/clustermesh/common" 19 "github.com/cilium/cilium/pkg/service/store" 20 ) 21 22 // meshPodInformer uses the ClusterServices to create fake pods based on each backends. 23 // This applies the labels `mesh.cilium.io/service-key` and `mesh.cilium.io/service-cluster` 24 // that corresponds to fake services created by meshServiceInformer. 25 type meshPodInformer struct { 26 dummyInformer 27 28 logger logrus.FieldLogger 29 globalServiceCache *common.GlobalServiceCache 30 handler cache.ResourceEventHandler 31 } 32 33 func newMeshPodInformer(logger logrus.FieldLogger, globalServiceCache *common.GlobalServiceCache) *meshPodInformer { 34 return &meshPodInformer{ 35 dummyInformer: dummyInformer{name: "meshPodInformer", logger: logger}, 36 globalServiceCache: globalServiceCache, 37 } 38 } 39 40 func getContainerPorts(portConfig store.PortConfiguration) []v1.ContainerPort { 41 ports := []v1.ContainerPort{} 42 for name, l4Addr := range portConfig { 43 ports = append(ports, v1.ContainerPort{ 44 Name: name, 45 ContainerPort: int32(l4Addr.Port), 46 Protocol: v1.Protocol(l4Addr.Protocol), 47 }) 48 } 49 return ports 50 } 51 52 func podObjectMeta(name string, clusterSvc *store.ClusterService) metav1.ObjectMeta { 53 return metav1.ObjectMeta{ 54 Name: name, 55 Namespace: clusterSvc.Namespace, 56 Labels: map[string]string{ 57 meshServiceNameLabel: clusterSvc.Name, 58 meshServiceClusterLabel: clusterSvc.Cluster, 59 }, 60 } 61 } 62 63 func podFromSingleAddr(addr string, portConfig store.PortConfiguration, clusterSvc *store.ClusterService) *v1.Pod { 64 return &v1.Pod{ 65 // The custom TypeMeta here is only used in logging inside the endpointslice reconciler 66 TypeMeta: metav1.TypeMeta{ 67 APIVersion: "cilium.io", 68 Kind: "store.ClusterService", 69 }, 70 ObjectMeta: podObjectMeta("fake-pod-"+addr, clusterSvc), 71 Spec: v1.PodSpec{ 72 NodeName: clusterSvc.Cluster, 73 Hostname: clusterSvc.Hostnames[addr], 74 Subdomain: clusterSvc.Name + "-" + clusterSvc.Cluster, 75 Containers: []v1.Container{{ 76 Ports: getContainerPorts(portConfig), 77 }}, 78 }, 79 Status: v1.PodStatus{ 80 Phase: v1.PodRunning, 81 Conditions: []v1.PodCondition{ 82 {Type: v1.PodReady, Status: v1.ConditionTrue}, 83 }, 84 PodIPs: []v1.PodIP{{IP: addr}}, 85 }, 86 } 87 } 88 89 func validateSelector(reqs labels.Requirements) (string, string, error) { 90 var name *string 91 var cluster *string 92 93 if len(reqs) != 2 { 94 goto err 95 } 96 97 for _, req := range reqs { 98 if req.Values().Len() != 1 || req.Operator() != selection.Equals { 99 goto err 100 } 101 102 if req.Key() == meshServiceNameLabel { 103 if name != nil { 104 goto err 105 } 106 name = &req.Values().List()[0] 107 } else if req.Key() == meshServiceClusterLabel { 108 if cluster != nil { 109 goto err 110 } 111 cluster = &req.Values().List()[0] 112 } 113 } 114 115 if name == nil || cluster == nil { 116 goto err 117 } 118 119 return *name, *cluster, nil 120 121 err: 122 return "", "", fmt.Errorf("meshPodInformer only supports listing with both service name and cluster as requirements: %s", reqs) 123 } 124 125 type meshPodLister struct { 126 informer *meshPodInformer 127 namespace string 128 } 129 130 func (l meshPodLister) List(selector labels.Selector) ([]*v1.Pod, error) { 131 reqs, _ := selector.Requirements() 132 name, cluster, err := validateSelector(reqs) 133 if err != nil { 134 return nil, err 135 } 136 137 clusterSvc := l.informer.globalServiceCache.GetService(types.NamespacedName{Name: name, Namespace: l.namespace}, cluster) 138 if clusterSvc == nil { 139 return nil, nil 140 } 141 142 pods := make([]*v1.Pod, 0, len(clusterSvc.Backends)) 143 for addr, portConfig := range clusterSvc.Backends { 144 pods = append(pods, podFromSingleAddr(addr, portConfig, clusterSvc)) 145 } 146 return pods, nil 147 } 148 149 func (i *meshPodInformer) AddEventHandler(handler cache.ResourceEventHandler) (cache.ResourceEventHandlerRegistration, error) { 150 i.handler = handler 151 return i, nil 152 } 153 154 func (i *meshPodInformer) onClusterServiceUpdate(clusterSvc *store.ClusterService) { 155 if i.handler == nil { 156 return 157 } 158 159 // We just need to notify the controller that there was an update so that 160 // the update on the related service + cluster is queued. 161 // The object sent here is not used and the type of event also doesn't matter. 162 i.handler.OnAdd( 163 &v1.Pod{ 164 ObjectMeta: podObjectMeta( 165 "dummy-notification-"+clusterSvc.Name+"-"+clusterSvc.Namespace+"-"+clusterSvc.Cluster, 166 clusterSvc, 167 ), 168 }, 169 false) 170 } 171 172 func (i *meshPodInformer) onClusterServiceDelete(clusterSvc *store.ClusterService) { 173 i.onClusterServiceUpdate(clusterSvc) 174 } 175 176 func (i meshPodInformer) HasSynced() bool { 177 // Controller is launched only after cluster mesh has been fully synced 178 // so we always return true here 179 return true 180 } 181 182 func (i *meshPodInformer) Pods(namespace string) listersv1.PodNamespaceLister { 183 return &meshPodLister{ 184 informer: i, namespace: namespace, 185 } 186 } 187 func (i *meshPodInformer) Informer() cache.SharedIndexInformer { 188 return i 189 } 190 func (i *meshPodInformer) Lister() listersv1.PodLister { 191 return i 192 } 193 194 func (i meshPodLister) Get(name string) (*v1.Pod, error) { 195 i.informer.logger.Error("called not implemented function meshPodLister.Get") 196 return nil, nil 197 } 198 func (i meshPodInformer) List(selector labels.Selector) ([]*v1.Pod, error) { 199 i.logger.Error("called not implemented function meshPodInformer.Get") 200 return nil, nil 201 }