istio.io/istio@v0.0.0-20240520182934-d79c90f27776/cni/pkg/nodeagent/pod_cache.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package nodeagent 16 17 import ( 18 "errors" 19 "fmt" 20 "path/filepath" 21 "runtime" 22 "sync" 23 24 corev1 "k8s.io/api/core/v1" 25 26 "istio.io/istio/pkg/maps" 27 "istio.io/istio/pkg/zdsapi" 28 ) 29 30 var ErrPodNotFound = errors.New("netns not provided, but is needed as pod is not in cache") 31 32 type PodNetnsCache interface { 33 ReadCurrentPodSnapshot() map[string]WorkloadInfo 34 } 35 36 // Hold a cache of node local pods with their netns 37 // if we don't know the netns, the pod will still be here with a nil netns. 38 type podNetnsCache struct { 39 openNetns func(nspath string) (NetnsCloser, error) 40 41 currentPodCache map[string]WorkloadInfo 42 mu sync.RWMutex 43 } 44 45 type WorkloadInfo struct { 46 Workload *zdsapi.WorkloadInfo 47 Netns NetnsCloser 48 } 49 50 var _ PodNetnsCache = &podNetnsCache{} 51 52 func newPodNetnsCache(openNetns func(nspath string) (NetnsCloser, error)) *podNetnsCache { 53 return &podNetnsCache{ 54 openNetns: openNetns, 55 currentPodCache: map[string]WorkloadInfo{}, 56 } 57 } 58 59 func (p *podNetnsCache) UpsertPodCache(pod *corev1.Pod, nspath string) (Netns, error) { 60 newnetns, err := p.openNetns(nspath) 61 if err != nil { 62 return nil, err 63 } 64 wl := WorkloadInfo{ 65 Workload: podToWorkload(pod), 66 Netns: newnetns, 67 } 68 return p.UpsertPodCacheWithNetns(string(pod.UID), wl), nil 69 } 70 71 // Update the cache with the given Netns. If there is already a Netns for the given uid, we return it, and close the one provided. 72 func (p *podNetnsCache) UpsertPodCacheWithNetns(uid string, workload WorkloadInfo) Netns { 73 // lock current snapshot pod map 74 p.mu.Lock() 75 defer p.mu.Unlock() 76 if existing := p.currentPodCache[uid]; existing.Netns != nil { 77 if existing.Netns.Inode() == workload.Netns.Inode() { 78 workload.Netns.Close() 79 // Replace the workload, but keep the old Netns 80 p.currentPodCache[uid] = WorkloadInfo{ 81 Workload: workload.Workload, 82 Netns: existing.Netns, 83 } 84 // already in cache 85 return existing.Netns 86 } 87 log.Debug("netns inode mismatch, using the new one") 88 } 89 90 p.addToCacheUnderLock(uid, workload) 91 return workload.Netns 92 } 93 94 // Update the cache with the given uid and nspath. Return the Netns for the given uid. 95 // If uid is already present, a cached Netns is returned, and the given nspath is ignored. 96 func (p *podNetnsCache) Get(uid string) Netns { 97 // lock current snapshot pod map 98 p.mu.RLock() 99 defer p.mu.RUnlock() 100 if info, f := p.currentPodCache[uid]; f { 101 return info.Netns 102 } 103 return nil 104 } 105 106 // make sure uid is in the cache, even if we don't have a netns 107 func (p *podNetnsCache) Ensure(uid string) { 108 p.mu.Lock() 109 defer p.mu.Unlock() 110 if _, ok := p.currentPodCache[uid]; !ok { 111 p.currentPodCache[uid] = WorkloadInfo{} 112 } 113 } 114 115 func (p *podNetnsCache) addToCacheUnderLock(uid string, workload WorkloadInfo) { 116 runtime.SetFinalizer(workload.Netns, closeNetns) 117 p.currentPodCache[uid] = workload 118 } 119 120 func closeNetns(netns NetnsCloser) { 121 netns.Close() 122 } 123 124 func (p *podNetnsCache) ReadCurrentPodSnapshot() map[string]WorkloadInfo { 125 p.mu.RLock() 126 defer p.mu.RUnlock() 127 // snapshot the cache to avoid long locking 128 return maps.Clone(p.currentPodCache) 129 } 130 131 // Remove and return the Netns for the given uid 132 // No need to return NetnsCloser here it will be closed automatically on GC. 133 // (it may be used in parallel by other parts of the code, so we want it to be used only when not used) 134 func (p *podNetnsCache) Take(uid string) Netns { 135 // lock current pod map 136 p.mu.Lock() 137 defer p.mu.Unlock() 138 if ns, ok := p.currentPodCache[uid]; ok { 139 delete(p.currentPodCache, uid) 140 // already in cache 141 return ns.Netns 142 } 143 144 return nil 145 } 146 147 func openNetnsInRoot(hostMountsPath string) func(nspath string) (NetnsCloser, error) { 148 return func(nspath string) (NetnsCloser, error) { 149 nspathInContainer := filepath.Join(hostMountsPath, nspath) 150 ns, err := OpenNetns(nspathInContainer) 151 if err != nil { 152 err = fmt.Errorf("failed to open netns: %w. Make sure that the netns host path %s is mounted in under %s in the container", err, nspath, hostMountsPath) 153 log.Error(err.Error()) 154 } 155 return ns, err 156 } 157 }