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 }