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  }