...

Source file src/k8s.io/kubernetes/pkg/volume/flexvolume/driver-call.go

Documentation: k8s.io/kubernetes/pkg/volume/flexvolume

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     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  	"time"
    24  
    25  	"k8s.io/klog/v2"
    26  
    27  	"k8s.io/kubernetes/pkg/volume"
    28  )
    29  
    30  const (
    31  	// Driver calls
    32  	initCmd          = "init"
    33  	getVolumeNameCmd = "getvolumename"
    34  
    35  	isAttached = "isattached"
    36  
    37  	attachCmd        = "attach"
    38  	waitForAttachCmd = "waitforattach"
    39  	mountDeviceCmd   = "mountdevice"
    40  
    41  	detachCmd        = "detach"
    42  	unmountDeviceCmd = "unmountdevice"
    43  
    44  	mountCmd   = "mount"
    45  	unmountCmd = "unmount"
    46  
    47  	expandVolumeCmd = "expandvolume"
    48  	expandFSCmd     = "expandfs"
    49  
    50  	// Option keys
    51  	optionFSType         = "kubernetes.io/fsType"
    52  	optionReadWrite      = "kubernetes.io/readwrite"
    53  	optionKeySecret      = "kubernetes.io/secret"
    54  	optionFSGroup        = "kubernetes.io/mounterArgs.FsGroup"
    55  	optionPVorVolumeName = "kubernetes.io/pvOrVolumeName"
    56  
    57  	optionKeyPodName      = "kubernetes.io/pod.name"
    58  	optionKeyPodNamespace = "kubernetes.io/pod.namespace"
    59  	optionKeyPodUID       = "kubernetes.io/pod.uid"
    60  
    61  	optionKeyServiceAccountName = "kubernetes.io/serviceAccount.name"
    62  )
    63  
    64  const (
    65  	// StatusSuccess represents the successful completion of command.
    66  	StatusSuccess = "Success"
    67  	// StatusNotSupported represents that the command is not supported.
    68  	StatusNotSupported = "Not supported"
    69  )
    70  
    71  var (
    72  	errTimeout = fmt.Errorf("timeout")
    73  )
    74  
    75  // DriverCall implements the basic contract between FlexVolume and its driver.
    76  // The caller is responsible for providing the required args.
    77  type DriverCall struct {
    78  	Command string
    79  	Timeout time.Duration
    80  	plugin  *flexVolumePlugin
    81  	args    []string
    82  }
    83  
    84  func (plugin *flexVolumePlugin) NewDriverCall(command string) *DriverCall {
    85  	return plugin.NewDriverCallWithTimeout(command, 0)
    86  }
    87  
    88  func (plugin *flexVolumePlugin) NewDriverCallWithTimeout(command string, timeout time.Duration) *DriverCall {
    89  	return &DriverCall{
    90  		Command: command,
    91  		Timeout: timeout,
    92  		plugin:  plugin,
    93  		args:    []string{command},
    94  	}
    95  }
    96  
    97  // Append appends arg into driver call argument list
    98  func (dc *DriverCall) Append(arg string) {
    99  	dc.args = append(dc.args, arg)
   100  }
   101  
   102  // AppendSpec appends volume spec to driver call argument list
   103  func (dc *DriverCall) AppendSpec(spec *volume.Spec, host volume.VolumeHost, extraOptions map[string]string) error {
   104  	optionsForDriver, err := NewOptionsForDriver(spec, host, extraOptions)
   105  	if err != nil {
   106  		return err
   107  	}
   108  
   109  	jsonBytes, err := json.Marshal(optionsForDriver)
   110  	if err != nil {
   111  		return fmt.Errorf("failed to marshal spec, error: %s", err.Error())
   112  	}
   113  
   114  	dc.Append(string(jsonBytes))
   115  	return nil
   116  }
   117  
   118  // Run executes the driver call
   119  func (dc *DriverCall) Run() (*DriverStatus, error) {
   120  	if dc.plugin.isUnsupported(dc.Command) {
   121  		return nil, errors.New(StatusNotSupported)
   122  	}
   123  	execPath := dc.plugin.getExecutable()
   124  
   125  	cmd := dc.plugin.runner.Command(execPath, dc.args...)
   126  
   127  	timeout := false
   128  	if dc.Timeout > 0 {
   129  		timer := time.AfterFunc(dc.Timeout, func() {
   130  			timeout = true
   131  			cmd.Stop()
   132  		})
   133  		defer timer.Stop()
   134  	}
   135  
   136  	output, execErr := cmd.CombinedOutput()
   137  	if execErr != nil {
   138  		if timeout {
   139  			return nil, errTimeout
   140  		}
   141  		_, err := handleCmdResponse(dc.Command, output)
   142  		if err == nil {
   143  			klog.Errorf("FlexVolume: driver bug: %s: exec error (%s) but no error in response.", execPath, execErr)
   144  			return nil, execErr
   145  		}
   146  		if isCmdNotSupportedErr(err) {
   147  			dc.plugin.unsupported(dc.Command)
   148  		} else {
   149  			klog.Warningf("FlexVolume: driver call failed: executable: %s, args: %s, error: %s, output: %q", execPath, dc.args, execErr.Error(), output)
   150  		}
   151  		return nil, err
   152  	}
   153  
   154  	status, err := handleCmdResponse(dc.Command, output)
   155  	if err != nil {
   156  		if isCmdNotSupportedErr(err) {
   157  			dc.plugin.unsupported(dc.Command)
   158  		}
   159  		return nil, err
   160  	}
   161  
   162  	return status, nil
   163  }
   164  
   165  // OptionsForDriver represents the spec given to the driver.
   166  type OptionsForDriver map[string]string
   167  
   168  // NewOptionsForDriver create driver options given volume spec
   169  func NewOptionsForDriver(spec *volume.Spec, host volume.VolumeHost, extraOptions map[string]string) (OptionsForDriver, error) {
   170  
   171  	volSourceFSType, err := getFSType(spec)
   172  	if err != nil {
   173  		return nil, err
   174  	}
   175  
   176  	readOnly, err := getReadOnly(spec)
   177  	if err != nil {
   178  		return nil, err
   179  	}
   180  
   181  	volSourceOptions, err := getOptions(spec)
   182  	if err != nil {
   183  		return nil, err
   184  	}
   185  
   186  	options := map[string]string{}
   187  
   188  	options[optionFSType] = volSourceFSType
   189  
   190  	if readOnly {
   191  		options[optionReadWrite] = "ro"
   192  	} else {
   193  		options[optionReadWrite] = "rw"
   194  	}
   195  
   196  	options[optionPVorVolumeName] = spec.Name()
   197  
   198  	for key, value := range extraOptions {
   199  		options[key] = value
   200  	}
   201  
   202  	for key, value := range volSourceOptions {
   203  		options[key] = value
   204  	}
   205  
   206  	return OptionsForDriver(options), nil
   207  }
   208  
   209  // DriverStatus represents the return value of the driver callout.
   210  type DriverStatus struct {
   211  	// Status of the callout. One of "Success", "Failure" or "Not supported".
   212  	Status string `json:"status"`
   213  	// Reason for success/failure.
   214  	Message string `json:"message,omitempty"`
   215  	// Path to the device attached. This field is valid only for attach calls.
   216  	// ie: /dev/sdx
   217  	DevicePath string `json:"device,omitempty"`
   218  	// Cluster wide unique name of the volume.
   219  	VolumeName string `json:"volumeName,omitempty"`
   220  	// Represents volume is attached on the node
   221  	Attached bool `json:"attached,omitempty"`
   222  	// Returns capabilities of the driver.
   223  	// By default we assume all the capabilities are supported.
   224  	// If the plugin does not support a capability, it can return false for that capability.
   225  	Capabilities *DriverCapabilities `json:",omitempty"`
   226  	// Returns the actual size of the volume after resizing is done, the size is in bytes.
   227  	ActualVolumeSize int64 `json:"volumeNewSize,omitempty"`
   228  }
   229  
   230  // DriverCapabilities represents what driver can do
   231  type DriverCapabilities struct {
   232  	Attach           bool `json:"attach"`
   233  	SELinuxRelabel   bool `json:"selinuxRelabel"`
   234  	SupportsMetrics  bool `json:"supportsMetrics"`
   235  	FSGroup          bool `json:"fsGroup"`
   236  	RequiresFSResize bool `json:"requiresFSResize"`
   237  }
   238  
   239  func defaultCapabilities() *DriverCapabilities {
   240  	return &DriverCapabilities{
   241  		Attach:           true,
   242  		SELinuxRelabel:   true,
   243  		SupportsMetrics:  false,
   244  		FSGroup:          true,
   245  		RequiresFSResize: true,
   246  	}
   247  }
   248  
   249  // isCmdNotSupportedErr checks if the error corresponds to command not supported by
   250  // driver.
   251  func isCmdNotSupportedErr(err error) bool {
   252  	return err != nil && err.Error() == StatusNotSupported
   253  }
   254  
   255  // handleCmdResponse processes the command output and returns the appropriate
   256  // error code or message.
   257  func handleCmdResponse(cmd string, output []byte) (*DriverStatus, error) {
   258  	status := DriverStatus{
   259  		Capabilities: defaultCapabilities(),
   260  	}
   261  	if err := json.Unmarshal(output, &status); err != nil {
   262  		klog.Errorf("Failed to unmarshal output for command: %s, output: %q, error: %s", cmd, string(output), err.Error())
   263  		return nil, err
   264  	} else if status.Status == StatusNotSupported {
   265  		klog.V(5).Infof("%s command is not supported by the driver", cmd)
   266  		return nil, errors.New(status.Status)
   267  	} else if status.Status != StatusSuccess {
   268  		errMsg := fmt.Sprintf("%s command failed, status: %s, reason: %s", cmd, status.Status, status.Message)
   269  		klog.Errorf(errMsg)
   270  		return nil, fmt.Errorf("%s", errMsg)
   271  	}
   272  
   273  	return &status, nil
   274  }
   275  

View as plain text