mirror of https://github.com/k3s-io/k3s
280 lines
7.6 KiB
Go
280 lines
7.6 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/fsGroup"
|
||
|
optionMountsDir = "kubernetes.io/mountsDir"
|
||
|
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
|
||
|
}
|