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  }