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 }