github.com/cilium/cilium@v1.16.2/pkg/ipam/metadata/manager.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package metadata
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  
    10  	"github.com/cilium/hive/cell"
    11  	"k8s.io/apimachinery/pkg/util/validation"
    12  
    13  	"github.com/cilium/cilium/daemon/k8s"
    14  	"github.com/cilium/cilium/pkg/annotation"
    15  	"github.com/cilium/cilium/pkg/ipam"
    16  	"github.com/cilium/cilium/pkg/k8s/resource"
    17  	slim_core_v1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/api/core/v1"
    18  	"github.com/cilium/cilium/pkg/logging"
    19  	"github.com/cilium/cilium/pkg/logging/logfields"
    20  )
    21  
    22  var log = logging.DefaultLogger.WithField(logfields.LogSubsys, "ipam-metadata-manager")
    23  
    24  type ManagerStoppedError struct{}
    25  
    26  func (m *ManagerStoppedError) Error() string {
    27  	return "ipam-metadata-manager has been stopped"
    28  }
    29  
    30  type ResourceNotFound struct {
    31  	Resource  string
    32  	Name      string
    33  	Namespace string
    34  }
    35  
    36  func (r *ResourceNotFound) Error() string {
    37  	name := r.Name
    38  	if r.Namespace != "" {
    39  		name = r.Namespace + "/" + r.Name
    40  	}
    41  	return fmt.Sprintf("resource %s %q not found", r.Resource, name)
    42  }
    43  
    44  func (r *ResourceNotFound) Is(target error) bool {
    45  	targetErr, ok := target.(*ResourceNotFound)
    46  	if !ok {
    47  		return false
    48  	}
    49  	if r != nil && targetErr.Resource != "" {
    50  		return r.Resource == targetErr.Resource
    51  	}
    52  	return true
    53  }
    54  
    55  type Manager interface {
    56  	GetIPPoolForPod(owner string, family ipam.Family) (pool string, err error)
    57  }
    58  
    59  type manager struct {
    60  	namespaceResource resource.Resource[*slim_core_v1.Namespace]
    61  	namespaceStore    resource.Store[*slim_core_v1.Namespace]
    62  	podResource       k8s.LocalPodResource
    63  	podStore          resource.Store[*slim_core_v1.Pod]
    64  }
    65  
    66  func (m *manager) Start(ctx cell.HookContext) (err error) {
    67  	m.namespaceStore, err = m.namespaceResource.Store(ctx)
    68  	if err != nil {
    69  		return fmt.Errorf("failed to obtain namespace store: %w", err)
    70  	}
    71  
    72  	m.podStore, err = m.podResource.Store(ctx)
    73  	if err != nil {
    74  		return fmt.Errorf("failed to obtain pod store: %w", err)
    75  	}
    76  
    77  	return nil
    78  }
    79  
    80  func (m *manager) Stop(ctx cell.HookContext) error {
    81  	m.namespaceStore = nil
    82  	m.podStore = nil
    83  	return nil
    84  }
    85  
    86  func splitK8sPodName(owner string) (namespace, name string, ok bool) {
    87  	// Require namespace/name format
    88  	namespace, name, ok = strings.Cut(owner, "/")
    89  	if !ok {
    90  		return "", "", false
    91  	}
    92  	// Check if components are a valid namespace name and pod name
    93  	if validation.IsDNS1123Subdomain(namespace) != nil ||
    94  		validation.IsDNS1123Subdomain(name) != nil {
    95  		return "", "", false
    96  	}
    97  	return namespace, name, true
    98  }
    99  
   100  func determinePoolByAnnotations(annotations map[string]string, family ipam.Family) (pool string, ok bool) {
   101  	switch family {
   102  	case ipam.IPv4:
   103  		if annotations[annotation.IPAMIPv4PoolKey] != "" {
   104  			return annotations[annotation.IPAMIPv4PoolKey], true
   105  		} else if annotations[annotation.IPAMPoolKey] != "" {
   106  			return annotations[annotation.IPAMPoolKey], true
   107  		}
   108  	case ipam.IPv6:
   109  		if annotations[annotation.IPAMIPv6PoolKey] != "" {
   110  			return annotations[annotation.IPAMIPv6PoolKey], true
   111  		} else if annotations[annotation.IPAMPoolKey] != "" {
   112  			return annotations[annotation.IPAMPoolKey], true
   113  		}
   114  	}
   115  
   116  	return "", false
   117  }
   118  
   119  func (m *manager) GetIPPoolForPod(owner string, family ipam.Family) (pool string, err error) {
   120  	if m.namespaceStore == nil || m.podStore == nil {
   121  		return "", &ManagerStoppedError{}
   122  	}
   123  
   124  	if family != ipam.IPv6 && family != ipam.IPv4 {
   125  		return "", fmt.Errorf("invalid IP family: %s", family)
   126  	}
   127  
   128  	namespace, name, ok := splitK8sPodName(owner)
   129  	if !ok {
   130  		log.WithField("owner", owner).
   131  			Debug("IPAM metadata request for invalid pod name, falling back to default pool")
   132  		return ipam.PoolDefault().String(), nil
   133  	}
   134  
   135  	// Check annotation on pod
   136  	pod, ok, err := m.podStore.GetByKey(resource.Key{
   137  		Name:      name,
   138  		Namespace: namespace,
   139  	})
   140  	if err != nil {
   141  		return "", fmt.Errorf("failed to lookup pod %q: %w", namespace+"/"+name, err)
   142  	} else if !ok {
   143  		return "", &ResourceNotFound{Resource: "Pod", Namespace: namespace, Name: name}
   144  	} else if ippool, ok := determinePoolByAnnotations(pod.Annotations, family); ok {
   145  		return ippool, nil
   146  	}
   147  
   148  	// Check annotation on namespace
   149  	podNamespace, ok, err := m.namespaceStore.GetByKey(resource.Key{
   150  		Name: namespace,
   151  	})
   152  	if err != nil {
   153  		return "", fmt.Errorf("failed to lookup namespace %q: %w", namespace, err)
   154  	} else if !ok {
   155  		return "", &ResourceNotFound{Resource: "Namespace", Name: namespace}
   156  	} else if ippool, ok := determinePoolByAnnotations(podNamespace.Annotations, family); ok {
   157  		return ippool, nil
   158  	}
   159  
   160  	// Fallback to default pool
   161  	return ipam.PoolDefault().String(), nil
   162  }