k8s.io/kubernetes@v1.29.3/pkg/volume/flexvolume/driver-call.go (about) 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 }