mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-11-04 07:49:35 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			279 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			279 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
Copyright 2017 The Kubernetes Authors.
 | 
						|
 | 
						|
Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
you may not use this file except in compliance with the License.
 | 
						|
You may obtain a copy of the License at
 | 
						|
 | 
						|
    http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
 | 
						|
Unless required by applicable law or agreed to in writing, software
 | 
						|
distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
See the License for the specific language governing permissions and
 | 
						|
limitations under the License.
 | 
						|
*/
 | 
						|
 | 
						|
package flexvolume
 | 
						|
 | 
						|
import (
 | 
						|
	"encoding/json"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"k8s.io/klog"
 | 
						|
 | 
						|
	"k8s.io/kubernetes/pkg/volume"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	// Driver calls
 | 
						|
	initCmd          = "init"
 | 
						|
	getVolumeNameCmd = "getvolumename"
 | 
						|
 | 
						|
	isAttached = "isattached"
 | 
						|
 | 
						|
	attachCmd        = "attach"
 | 
						|
	waitForAttachCmd = "waitforattach"
 | 
						|
	mountDeviceCmd   = "mountdevice"
 | 
						|
 | 
						|
	detachCmd        = "detach"
 | 
						|
	unmountDeviceCmd = "unmountdevice"
 | 
						|
 | 
						|
	mountCmd   = "mount"
 | 
						|
	unmountCmd = "unmount"
 | 
						|
 | 
						|
	expandVolumeCmd = "expandvolume"
 | 
						|
	expandFSCmd     = "expandfs"
 | 
						|
 | 
						|
	// Option keys
 | 
						|
	optionFSType         = "kubernetes.io/fsType"
 | 
						|
	optionReadWrite      = "kubernetes.io/readwrite"
 | 
						|
	optionKeySecret      = "kubernetes.io/secret"
 | 
						|
	optionFSGroup        = "kubernetes.io/mounterArgs.FsGroup"
 | 
						|
	optionPVorVolumeName = "kubernetes.io/pvOrVolumeName"
 | 
						|
 | 
						|
	optionKeyPodName      = "kubernetes.io/pod.name"
 | 
						|
	optionKeyPodNamespace = "kubernetes.io/pod.namespace"
 | 
						|
	optionKeyPodUID       = "kubernetes.io/pod.uid"
 | 
						|
 | 
						|
	optionKeyServiceAccountName = "kubernetes.io/serviceAccount.name"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	// StatusSuccess represents the successful completion of command.
 | 
						|
	StatusSuccess = "Success"
 | 
						|
	// StatusNotSupported represents that the command is not supported.
 | 
						|
	StatusNotSupported = "Not supported"
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	errTimeout = fmt.Errorf("Timeout")
 | 
						|
)
 | 
						|
 | 
						|
// DriverCall implements the basic contract between FlexVolume and its driver.
 | 
						|
// The caller is responsible for providing the required args.
 | 
						|
type DriverCall struct {
 | 
						|
	Command string
 | 
						|
	Timeout time.Duration
 | 
						|
	plugin  *flexVolumePlugin
 | 
						|
	args    []string
 | 
						|
}
 | 
						|
 | 
						|
func (plugin *flexVolumePlugin) NewDriverCall(command string) *DriverCall {
 | 
						|
	return plugin.NewDriverCallWithTimeout(command, 0)
 | 
						|
}
 | 
						|
 | 
						|
func (plugin *flexVolumePlugin) NewDriverCallWithTimeout(command string, timeout time.Duration) *DriverCall {
 | 
						|
	return &DriverCall{
 | 
						|
		Command: command,
 | 
						|
		Timeout: timeout,
 | 
						|
		plugin:  plugin,
 | 
						|
		args:    []string{command},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Append appends arg into driver call argument list
 | 
						|
func (dc *DriverCall) Append(arg string) {
 | 
						|
	dc.args = append(dc.args, arg)
 | 
						|
}
 | 
						|
 | 
						|
// AppendSpec appends volume spec to driver call argument list
 | 
						|
func (dc *DriverCall) AppendSpec(spec *volume.Spec, host volume.VolumeHost, extraOptions map[string]string) error {
 | 
						|
	optionsForDriver, err := NewOptionsForDriver(spec, host, extraOptions)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	jsonBytes, err := json.Marshal(optionsForDriver)
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("Failed to marshal spec, error: %s", err.Error())
 | 
						|
	}
 | 
						|
 | 
						|
	dc.Append(string(jsonBytes))
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// Run executes the driver call
 | 
						|
func (dc *DriverCall) Run() (*DriverStatus, error) {
 | 
						|
	if dc.plugin.isUnsupported(dc.Command) {
 | 
						|
		return nil, errors.New(StatusNotSupported)
 | 
						|
	}
 | 
						|
	execPath := dc.plugin.getExecutable()
 | 
						|
 | 
						|
	cmd := dc.plugin.runner.Command(execPath, dc.args...)
 | 
						|
 | 
						|
	timeout := false
 | 
						|
	if dc.Timeout > 0 {
 | 
						|
		timer := time.AfterFunc(dc.Timeout, func() {
 | 
						|
			timeout = true
 | 
						|
			cmd.Stop()
 | 
						|
		})
 | 
						|
		defer timer.Stop()
 | 
						|
	}
 | 
						|
 | 
						|
	output, execErr := cmd.CombinedOutput()
 | 
						|
	if execErr != nil {
 | 
						|
		if timeout {
 | 
						|
			return nil, errTimeout
 | 
						|
		}
 | 
						|
		_, err := handleCmdResponse(dc.Command, output)
 | 
						|
		if err == nil {
 | 
						|
			klog.Errorf("FlexVolume: driver bug: %s: exec error (%s) but no error in response.", execPath, execErr)
 | 
						|
			return nil, execErr
 | 
						|
		}
 | 
						|
		if isCmdNotSupportedErr(err) {
 | 
						|
			dc.plugin.unsupported(dc.Command)
 | 
						|
		} else {
 | 
						|
			klog.Warningf("FlexVolume: driver call failed: executable: %s, args: %s, error: %s, output: %q", execPath, dc.args, execErr.Error(), output)
 | 
						|
		}
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	status, err := handleCmdResponse(dc.Command, output)
 | 
						|
	if err != nil {
 | 
						|
		if isCmdNotSupportedErr(err) {
 | 
						|
			dc.plugin.unsupported(dc.Command)
 | 
						|
		}
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	return status, nil
 | 
						|
}
 | 
						|
 | 
						|
// OptionsForDriver represents the spec given to the driver.
 | 
						|
type OptionsForDriver map[string]string
 | 
						|
 | 
						|
// NewOptionsForDriver create driver options given volume spec
 | 
						|
func NewOptionsForDriver(spec *volume.Spec, host volume.VolumeHost, extraOptions map[string]string) (OptionsForDriver, error) {
 | 
						|
 | 
						|
	volSourceFSType, err := getFSType(spec)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	readOnly, err := getReadOnly(spec)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	volSourceOptions, err := getOptions(spec)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	options := map[string]string{}
 | 
						|
 | 
						|
	options[optionFSType] = volSourceFSType
 | 
						|
 | 
						|
	if readOnly {
 | 
						|
		options[optionReadWrite] = "ro"
 | 
						|
	} else {
 | 
						|
		options[optionReadWrite] = "rw"
 | 
						|
	}
 | 
						|
 | 
						|
	options[optionPVorVolumeName] = spec.Name()
 | 
						|
 | 
						|
	for key, value := range extraOptions {
 | 
						|
		options[key] = value
 | 
						|
	}
 | 
						|
 | 
						|
	for key, value := range volSourceOptions {
 | 
						|
		options[key] = value
 | 
						|
	}
 | 
						|
 | 
						|
	return OptionsForDriver(options), nil
 | 
						|
}
 | 
						|
 | 
						|
// DriverStatus represents the return value of the driver callout.
 | 
						|
type DriverStatus struct {
 | 
						|
	// Status of the callout. One of "Success", "Failure" or "Not supported".
 | 
						|
	Status string `json:"status"`
 | 
						|
	// Reason for success/failure.
 | 
						|
	Message string `json:"message,omitempty"`
 | 
						|
	// Path to the device attached. This field is valid only for attach calls.
 | 
						|
	// ie: /dev/sdx
 | 
						|
	DevicePath string `json:"device,omitempty"`
 | 
						|
	// Cluster wide unique name of the volume.
 | 
						|
	VolumeName string `json:"volumeName,omitempty"`
 | 
						|
	// Represents volume is attached on the node
 | 
						|
	Attached bool `json:"attached,omitempty"`
 | 
						|
	// Returns capabilities of the driver.
 | 
						|
	// By default we assume all the capabilities are supported.
 | 
						|
	// If the plugin does not support a capability, it can return false for that capability.
 | 
						|
	Capabilities *DriverCapabilities `json:",omitempty"`
 | 
						|
	// Returns the actual size of the volume after resizing is done, the size is in bytes.
 | 
						|
	ActualVolumeSize int64 `json:"volumeNewSize,omitempty"`
 | 
						|
}
 | 
						|
 | 
						|
// DriverCapabilities represents what driver can do
 | 
						|
type DriverCapabilities struct {
 | 
						|
	Attach           bool `json:"attach"`
 | 
						|
	SELinuxRelabel   bool `json:"selinuxRelabel"`
 | 
						|
	SupportsMetrics  bool `json:"supportsMetrics"`
 | 
						|
	FSGroup          bool `json:"fsGroup"`
 | 
						|
	RequiresFSResize bool `json:"requiresFSResize"`
 | 
						|
}
 | 
						|
 | 
						|
func defaultCapabilities() *DriverCapabilities {
 | 
						|
	return &DriverCapabilities{
 | 
						|
		Attach:           true,
 | 
						|
		SELinuxRelabel:   true,
 | 
						|
		SupportsMetrics:  false,
 | 
						|
		FSGroup:          true,
 | 
						|
		RequiresFSResize: true,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// isCmdNotSupportedErr checks if the error corresponds to command not supported by
 | 
						|
// driver.
 | 
						|
func isCmdNotSupportedErr(err error) bool {
 | 
						|
	if err != nil && err.Error() == StatusNotSupported {
 | 
						|
		return true
 | 
						|
	}
 | 
						|
 | 
						|
	return false
 | 
						|
}
 | 
						|
 | 
						|
// handleCmdResponse processes the command output and returns the appropriate
 | 
						|
// error code or message.
 | 
						|
func handleCmdResponse(cmd string, output []byte) (*DriverStatus, error) {
 | 
						|
	status := DriverStatus{
 | 
						|
		Capabilities: defaultCapabilities(),
 | 
						|
	}
 | 
						|
	if err := json.Unmarshal(output, &status); err != nil {
 | 
						|
		klog.Errorf("Failed to unmarshal output for command: %s, output: %q, error: %s", cmd, string(output), err.Error())
 | 
						|
		return nil, err
 | 
						|
	} else if status.Status == StatusNotSupported {
 | 
						|
		klog.V(5).Infof("%s command is not supported by the driver", cmd)
 | 
						|
		return nil, errors.New(status.Status)
 | 
						|
	} else if status.Status != StatusSuccess {
 | 
						|
		errMsg := fmt.Sprintf("%s command failed, status: %s, reason: %s", cmd, status.Status, status.Message)
 | 
						|
		klog.Errorf(errMsg)
 | 
						|
		return nil, fmt.Errorf("%s", errMsg)
 | 
						|
	}
 | 
						|
 | 
						|
	return &status, nil
 | 
						|
}
 |