123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280 |
- /*
- 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"
- 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
- }
|