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  }