github.com/mirantis/virtlet@v1.5.2-0.20191204181327-1659b8a48e9b/pkg/flexvolume/flexvolume.go (about) 1 /* 2 Copyright 2017 Mirantis 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package flexvolume 18 19 import ( 20 "encoding/json" 21 "errors" 22 "fmt" 23 "os" 24 "path/filepath" 25 "strconv" 26 "strings" 27 28 "github.com/Mirantis/virtlet/pkg/fs" 29 "github.com/Mirantis/virtlet/pkg/utils" 30 ) 31 32 const ( 33 uuidOptionsKey = "uuid" 34 partOptionsKey = "part" 35 flexvolumeDataFile = "virtlet-flexvolume.json" 36 ) 37 38 // flexVolumeDebug indicates whether flexvolume debugging should be enabled 39 var flexVolumeDebug = false 40 41 func init() { 42 // XXX: invent a better way to decide whether debugging should 43 // be used for flexvolume driver. For now we only enable it if 44 // Docker-in-Docker env is used 45 if fi, err := os.Stat("/dind/flexvolume_driver"); err == nil && !fi.IsDir() { 46 flexVolumeDebug = true 47 } 48 } 49 50 // UUIDGen type function returns newly generated UUIDv4 as a string 51 type UUIDGen func() string 52 53 // Driver provides a virtlet specific implementation of 54 // https://kubernetes.io/docs/concepts/storage/volumes/#flexVolume 55 type Driver struct { 56 uuidGen UUIDGen 57 fs fs.FileSystem 58 } 59 60 // NewDriver creates a Driver struct 61 func NewDriver(uuidGen UUIDGen, fs fs.FileSystem) *Driver { 62 return &Driver{uuidGen: uuidGen, fs: fs} 63 } 64 65 func (d *Driver) populateVolumeDir(targetDir string, opts map[string]interface{}) error { 66 return utils.WriteJSON(filepath.Join(targetDir, flexvolumeDataFile), opts, 0700) 67 } 68 69 // The following functions are not currently needed, but still 70 // keeping them to make it easier to actually implement them 71 72 // Invocation: <driver executable> init 73 func (d *Driver) init() (map[string]interface{}, error) { 74 return nil, nil 75 } 76 77 // Invocation: <driver executable> attach <json options> <node name> 78 func (d *Driver) attach(jsonOptions, nodeName string) (map[string]interface{}, error) { 79 return nil, nil 80 } 81 82 // Invocation: <driver executable> detach <mount device> <node name> 83 func (d *Driver) detach(mountDev, nodeName string) (map[string]interface{}, error) { 84 return nil, nil 85 } 86 87 // Invocation: <driver executable> waitforattach <mount device> <json options> 88 func (d *Driver) waitForAttach(mountDev, jsonOptions string) (map[string]interface{}, error) { 89 return map[string]interface{}{"device": mountDev}, nil 90 } 91 92 // Invocation: <driver executable> isattached <json options> <node name> 93 func (d *Driver) isAttached(jsonOptions, nodeName string) (map[string]interface{}, error) { 94 return map[string]interface{}{"attached": true}, nil 95 } 96 97 //Invocation: <driver executable> mount <target mount dir> <json options> 98 func (d *Driver) mount(targetMountDir, jsonOptions string) (map[string]interface{}, error) { 99 var opts map[string]interface{} 100 if err := json.Unmarshal([]byte(jsonOptions), &opts); err != nil { 101 return nil, fmt.Errorf("failed to unmarshal json options: %v", err) 102 } 103 opts[uuidOptionsKey] = d.uuidGen() 104 if err := os.MkdirAll(targetMountDir, 0700); err != nil { 105 return nil, fmt.Errorf("os.MkDirAll(): %v", err) 106 } 107 108 // Here we populate the volume directory twice. 109 // That's because we need to do tmpfs mount to make kubelet happy - 110 // it will not try to unmount the volume if it doesn't detect mount 111 // point on the flexvolume dir, and using 'mount --bind' is not enough 112 // because kubelet's mount point detection doesn't see bind mounts. 113 // The problem is that hostPaths are mounted as private (no mount 114 // propagation) and so tmpfs content is not visible inside Virtlet 115 // pod. So, in order to avoid unneeded confusion down the road, 116 // we place flexvolume contents to the volume dir twice, 117 // both directly and onto the freshly mounted tmpfs. 118 119 if err := d.populateVolumeDir(targetMountDir, opts); err != nil { 120 return nil, err 121 } 122 123 if err := d.fs.Mount("tmpfs", targetMountDir, "tmpfs", false); err != nil { 124 return nil, fmt.Errorf("error mounting tmpfs at %q: %v", targetMountDir, err) 125 } 126 127 done := false 128 defer func() { 129 // try to unmount upon error or panic 130 if !done { 131 d.fs.Unmount(targetMountDir, true) 132 } 133 }() 134 135 if err := d.populateVolumeDir(targetMountDir, opts); err != nil { 136 return nil, err 137 } 138 139 done = true 140 return nil, nil 141 } 142 143 // Invocation: <driver executable> unmount <mount dir> 144 func (d *Driver) unmount(targetMountDir string) (map[string]interface{}, error) { 145 if err := d.fs.Unmount(targetMountDir, true); err != nil { 146 return nil, fmt.Errorf("unmount %q: %v", targetMountDir, err.Error()) 147 } 148 149 if err := os.RemoveAll(targetMountDir); err != nil { 150 return nil, fmt.Errorf("os.RemoveAll(): %v", err.Error()) 151 } 152 153 return nil, nil 154 } 155 156 type driverOp func(*Driver, []string) (map[string]interface{}, error) 157 158 type cmdInfo struct { 159 numArgs int 160 run driverOp 161 } 162 163 var commands = map[string]cmdInfo{ 164 "init": { 165 0, func(d *Driver, args []string) (map[string]interface{}, error) { 166 return d.init() 167 }, 168 }, 169 "attach": { 170 2, func(d *Driver, args []string) (map[string]interface{}, error) { 171 return d.attach(args[0], args[1]) 172 }, 173 }, 174 "detach": { 175 2, func(d *Driver, args []string) (map[string]interface{}, error) { 176 return d.detach(args[0], args[1]) 177 }, 178 }, 179 "waitforattach": { 180 2, func(d *Driver, args []string) (map[string]interface{}, error) { 181 return d.waitForAttach(args[0], args[1]) 182 }, 183 }, 184 "isattached": { 185 2, func(d *Driver, args []string) (map[string]interface{}, error) { 186 return d.isAttached(args[0], args[1]) 187 }, 188 }, 189 "mount": { 190 2, func(d *Driver, args []string) (map[string]interface{}, error) { 191 return d.mount(args[0], args[1]) 192 }, 193 }, 194 "unmount": { 195 1, func(d *Driver, args []string) (map[string]interface{}, error) { 196 return d.unmount(args[0]) 197 }, 198 }, 199 } 200 201 func (d *Driver) doRun(args []string) (map[string]interface{}, error) { 202 if len(args) == 0 { 203 return nil, errors.New("no arguments passed to flexvolume driver") 204 } 205 nArgs := len(args) - 1 206 op := args[0] 207 if cmdInfo, found := commands[op]; found { 208 if cmdInfo.numArgs == nArgs { 209 return cmdInfo.run(d, args[1:]) 210 } 211 return nil, fmt.Errorf("unexpected number of args %d (expected %d) for operation %q", nArgs, cmdInfo.numArgs, op) 212 } 213 return map[string]interface{}{ 214 "status": "Not supported", 215 }, nil 216 } 217 218 // Run runs the driver 219 func (d *Driver) Run(args []string) string { 220 r := formatResult(d.doRun(args)) 221 222 if flexVolumeDebug { 223 // This is for debugging purposes only. 224 // The problem is that kubelet grabs CombinedOutput() from the process 225 // and tries to parse it as JSON (need to recheck this, 226 // maybe submit a PS to fix it) 227 f, err := os.OpenFile("/tmp/flexvolume.log", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0666) 228 if err == nil { 229 defer f.Close() 230 fmt.Fprintf(f, "flexvolume %s -> %s\n", strings.Join(args, " "), r) 231 } 232 } 233 234 return r 235 } 236 237 func formatResult(fields map[string]interface{}, err error) string { 238 var data map[string]interface{} 239 if err != nil { 240 data = map[string]interface{}{ 241 "status": "Failure", 242 "message": err.Error(), 243 } 244 } else { 245 data = map[string]interface{}{ 246 "status": "Success", 247 } 248 for k, v := range fields { 249 data[k] = v 250 } 251 } 252 s, err := json.Marshal(data) 253 if err != nil { 254 panic("error marshalling the data") 255 } 256 return string(s) + "\n" 257 } 258 259 // GetFlexvolumeInfo tries to extract flexvolume uuid and partition 260 // number from the specified directory. Negative partition number 261 // means that no partition number was specified. 262 func GetFlexvolumeInfo(dir string) (string, int, error) { 263 dataFile := filepath.Join(dir, flexvolumeDataFile) 264 if _, err := os.Stat(dataFile); os.IsNotExist(err) { 265 return "", 0, err 266 } 267 268 var opts map[string]interface{} 269 if err := utils.ReadJSON(dataFile, &opts); err != nil { 270 return "", 0, fmt.Errorf("can't read flexvolume data file %q: %v", dataFile, err) 271 } 272 uuidRaw, found := opts[uuidOptionsKey] 273 if !found { 274 return "", 0, fmt.Errorf("%q: flexvolume doesn't have an uuid", dataFile) 275 } 276 uuid, ok := uuidRaw.(string) 277 if !ok { 278 return "", 0, fmt.Errorf("%q: flexvolume uuid is not a string", dataFile) 279 } 280 part := -1 281 partRaw, found := opts[partOptionsKey] 282 if found { 283 partStr, ok := partRaw.(string) 284 if !ok { 285 return "", 0, fmt.Errorf("%q: 'part' value is not a string", dataFile) 286 } 287 var err error 288 part, err = strconv.Atoi(partStr) 289 if err != nil { 290 return "", 0, fmt.Errorf("%q: malformed 'part' value (partition number): %q: %v", dataFile, partRaw, err) 291 } 292 } 293 return uuid, part, nil 294 }