github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/shim/utils/volumes.go (about)

     1  // Copyright 2018 The gVisor 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  //     https://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 utils
    16  
    17  import (
    18  	"fmt"
    19  	"path/filepath"
    20  	"strings"
    21  
    22  	specs "github.com/opencontainers/runtime-spec/specs-go"
    23  )
    24  
    25  const volumeKeyPrefix = "dev.gvisor.spec.mount."
    26  
    27  var kubeletPodsDir = "/var/lib/kubelet/pods"
    28  
    29  // volumeName gets volume name from volume annotation key, example:
    30  //	dev.gvisor.spec.mount.NAME.share
    31  func volumeName(k string) string {
    32  	return strings.SplitN(strings.TrimPrefix(k, volumeKeyPrefix), ".", 2)[0]
    33  }
    34  
    35  // volumeFieldName gets volume field name from volume annotation key, example:
    36  //	`type` is the field of dev.gvisor.spec.mount.NAME.type
    37  func volumeFieldName(k string) string {
    38  	parts := strings.Split(strings.TrimPrefix(k, volumeKeyPrefix), ".")
    39  	return parts[len(parts)-1]
    40  }
    41  
    42  // podUID gets pod UID from the pod log path.
    43  func podUID(s *specs.Spec) (string, error) {
    44  	sandboxLogDir := s.Annotations[sandboxLogDirAnnotation]
    45  	if sandboxLogDir == "" {
    46  		return "", fmt.Errorf("no sandbox log path annotation")
    47  	}
    48  	fields := strings.Split(filepath.Base(sandboxLogDir), "_")
    49  	switch len(fields) {
    50  	case 1: // This is the old CRI logging path.
    51  		return fields[0], nil
    52  	case 3: // This is the new CRI logging path.
    53  		return fields[2], nil
    54  	}
    55  	return "", fmt.Errorf("unexpected sandbox log path %q", sandboxLogDir)
    56  }
    57  
    58  // isVolumeKey checks whether an annotation key is for volume.
    59  func isVolumeKey(k string) bool {
    60  	return strings.HasPrefix(k, volumeKeyPrefix)
    61  }
    62  
    63  // volumeSourceKey constructs the annotation key for volume source.
    64  func volumeSourceKey(volume string) string {
    65  	return volumeKeyPrefix + volume + ".source"
    66  }
    67  
    68  // volumePath searches the volume path in the kubelet pod directory.
    69  func volumePath(volume, uid string) (string, error) {
    70  	// TODO: Support subpath when gvisor supports pod volume bind mount.
    71  	volumeSearchPath := fmt.Sprintf("%s/%s/volumes/*/%s", kubeletPodsDir, uid, volume)
    72  	dirs, err := filepath.Glob(volumeSearchPath)
    73  	if err != nil {
    74  		return "", err
    75  	}
    76  	if len(dirs) != 1 {
    77  		return "", fmt.Errorf("unexpected matched volume list %v", dirs)
    78  	}
    79  	return dirs[0], nil
    80  }
    81  
    82  // isVolumePath checks whether a string is the volume path.
    83  func isVolumePath(volume, path string) (bool, error) {
    84  	// TODO: Support subpath when gvisor supports pod volume bind mount.
    85  	volumeSearchPath := fmt.Sprintf("%s/*/volumes/*/%s", kubeletPodsDir, volume)
    86  	return filepath.Match(volumeSearchPath, path)
    87  }
    88  
    89  // UpdateVolumeAnnotations add necessary OCI annotations for gvisor
    90  // volume optimization. Returns true if the spec was modified.
    91  func UpdateVolumeAnnotations(s *specs.Spec) (bool, error) {
    92  	var uid string
    93  	if IsSandbox(s) {
    94  		var err error
    95  		uid, err = podUID(s)
    96  		if err != nil {
    97  			// Skip if we can't get pod UID, because this doesn't work
    98  			// for containerd 1.1.
    99  			return false, nil
   100  		}
   101  	}
   102  	var updated bool
   103  	for k, v := range s.Annotations {
   104  		if !isVolumeKey(k) {
   105  			continue
   106  		}
   107  		if volumeFieldName(k) != "type" {
   108  			continue
   109  		}
   110  		volume := volumeName(k)
   111  		if uid != "" {
   112  			// This is a sandbox.
   113  			path, err := volumePath(volume, uid)
   114  			if err != nil {
   115  				return false, fmt.Errorf("get volume path for %q: %w", volume, err)
   116  			}
   117  			s.Annotations[volumeSourceKey(volume)] = path
   118  			updated = true
   119  		} else {
   120  			// This is a container.
   121  			for i := range s.Mounts {
   122  				// An error is returned for sandbox if source annotation is not
   123  				// successfully applied, so it is guaranteed that the source annotation
   124  				// for sandbox has already been successfully applied at this point.
   125  				//
   126  				// The volume name is unique inside a pod, so matching without podUID
   127  				// is fine here.
   128  				//
   129  				// TODO: Pass podUID down to shim for containers to do more accurate
   130  				// matching.
   131  				if yes, _ := isVolumePath(volume, s.Mounts[i].Source); yes {
   132  					// Container mount type must match the sandbox's mount type.
   133  					changeMountType(&s.Mounts[i], v)
   134  					updated = true
   135  				}
   136  			}
   137  		}
   138  	}
   139  	return updated, nil
   140  }
   141  
   142  func changeMountType(m *specs.Mount, newType string) {
   143  	m.Type = newType
   144  
   145  	// OCI spec allows bind mounts to be specified in options only. So if new type
   146  	// is not bind, remove bind/rbind from options.
   147  	//
   148  	// "For bind mounts (when options include either bind or rbind), the type is
   149  	// a dummy, often "none" (not listed in /proc/filesystems)."
   150  	if newType != "bind" {
   151  		newOpts := make([]string, 0, len(m.Options))
   152  		for _, opt := range m.Options {
   153  			if opt != "rbind" && opt != "bind" {
   154  				newOpts = append(newOpts, opt)
   155  			}
   156  		}
   157  		m.Options = newOpts
   158  	}
   159  }