github.com/AbhinandanKurakure/podman/v3@v3.4.10/libpod/plugin/volume_api.go (about) 1 package plugin 2 3 import ( 4 "bytes" 5 "context" 6 "io/ioutil" 7 "net" 8 "net/http" 9 "os" 10 "path/filepath" 11 "strings" 12 "sync" 13 "time" 14 15 "github.com/containers/podman/v3/libpod/define" 16 "github.com/docker/go-plugins-helpers/sdk" 17 "github.com/docker/go-plugins-helpers/volume" 18 jsoniter "github.com/json-iterator/go" 19 "github.com/pkg/errors" 20 "github.com/sirupsen/logrus" 21 ) 22 23 var json = jsoniter.ConfigCompatibleWithStandardLibrary 24 25 // TODO: We should add syntax for specifying plugins to containers.conf, and 26 // support for loading based on that. 27 28 // Copied from docker/go-plugins-helpers/volume/api.go - not exported, so we 29 // need to do this to get at them. 30 // These are well-established paths that should not change unless the plugin API 31 // version changes. 32 var ( 33 activatePath = "/Plugin.Activate" 34 createPath = "/VolumeDriver.Create" 35 getPath = "/VolumeDriver.Get" 36 listPath = "/VolumeDriver.List" 37 removePath = "/VolumeDriver.Remove" 38 hostVirtualPath = "/VolumeDriver.Path" 39 mountPath = "/VolumeDriver.Mount" 40 unmountPath = "/VolumeDriver.Unmount" 41 // nolint 42 capabilitiesPath = "/VolumeDriver.Capabilities" 43 ) 44 45 const ( 46 defaultTimeout = 5 * time.Second 47 volumePluginType = "VolumeDriver" 48 ) 49 50 var ( 51 ErrNotPlugin = errors.New("target does not appear to be a valid plugin") 52 ErrNotVolumePlugin = errors.New("plugin is not a volume plugin") 53 ErrPluginRemoved = errors.New("plugin is no longer available (shut down?)") 54 55 // This stores available, initialized volume plugins. 56 pluginsLock sync.Mutex 57 plugins map[string]*VolumePlugin 58 ) 59 60 // VolumePlugin is a single volume plugin. 61 type VolumePlugin struct { 62 // Name is the name of the volume plugin. This will be used to refer to 63 // it. 64 Name string 65 // SocketPath is the unix socket at which the plugin is accessed. 66 SocketPath string 67 // Client is the HTTP client we use to connect to the plugin. 68 Client *http.Client 69 } 70 71 // This is the response from the activate endpoint of the API. 72 type activateResponse struct { 73 Implements []string 74 } 75 76 // Validate that the given plugin is good to use. 77 // Add it to available plugins if so. 78 func validatePlugin(newPlugin *VolumePlugin) error { 79 // It's a socket. Is it a plugin? 80 // Hit the Activate endpoint to find out if it is, and if so what kind 81 req, err := http.NewRequest("POST", "http://plugin"+activatePath, nil) 82 if err != nil { 83 return errors.Wrapf(err, "error making request to volume plugin %s activation endpoint", newPlugin.Name) 84 } 85 86 req.Header.Set("Host", newPlugin.getURI()) 87 req.Header.Set("Content-Type", sdk.DefaultContentTypeV1_1) 88 89 resp, err := newPlugin.Client.Do(req) 90 if err != nil { 91 return errors.Wrapf(err, "error sending request to plugin %s activation endpoint", newPlugin.Name) 92 } 93 defer resp.Body.Close() 94 95 // Response code MUST be 200. Anything else, we have to assume it's not 96 // a valid plugin. 97 if resp.StatusCode != 200 { 98 return errors.Wrapf(ErrNotPlugin, "got status code %d from activation endpoint for plugin %s", resp.StatusCode, newPlugin.Name) 99 } 100 101 // Read and decode the body so we can tell if this is a volume plugin. 102 respBytes, err := ioutil.ReadAll(resp.Body) 103 if err != nil { 104 return errors.Wrapf(err, "error reading activation response body from plugin %s", newPlugin.Name) 105 } 106 107 respStruct := new(activateResponse) 108 if err := json.Unmarshal(respBytes, respStruct); err != nil { 109 return errors.Wrapf(err, "error unmarshalling plugin %s activation response", newPlugin.Name) 110 } 111 112 foundVolume := false 113 for _, pluginType := range respStruct.Implements { 114 if pluginType == volumePluginType { 115 foundVolume = true 116 break 117 } 118 } 119 120 if !foundVolume { 121 return errors.Wrapf(ErrNotVolumePlugin, "plugin %s does not implement volume plugin, instead provides %s", newPlugin.Name, strings.Join(respStruct.Implements, ", ")) 122 } 123 124 if plugins == nil { 125 plugins = make(map[string]*VolumePlugin) 126 } 127 128 plugins[newPlugin.Name] = newPlugin 129 130 return nil 131 } 132 133 // GetVolumePlugin gets a single volume plugin, with the given name, at the 134 // given path. 135 func GetVolumePlugin(name string, path string) (*VolumePlugin, error) { 136 pluginsLock.Lock() 137 defer pluginsLock.Unlock() 138 139 plugin, exists := plugins[name] 140 if exists { 141 // This shouldn't be possible, but just in case... 142 if plugin.SocketPath != filepath.Clean(path) { 143 return nil, errors.Wrapf(define.ErrInvalidArg, "requested path %q for volume plugin %s does not match pre-existing path for plugin, %q", path, name, plugin.SocketPath) 144 } 145 146 return plugin, nil 147 } 148 149 // It's not cached. We need to get it. 150 151 newPlugin := new(VolumePlugin) 152 newPlugin.Name = name 153 newPlugin.SocketPath = filepath.Clean(path) 154 155 // Need an HTTP client to force a Unix connection. 156 // And since we can reuse it, might as well cache it. 157 client := new(http.Client) 158 client.Timeout = defaultTimeout 159 // This bit borrowed from pkg/bindings/connection.go 160 client.Transport = &http.Transport{ 161 DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { 162 return (&net.Dialer{}).DialContext(ctx, "unix", newPlugin.SocketPath) 163 }, 164 DisableCompression: true, 165 } 166 newPlugin.Client = client 167 168 stat, err := os.Stat(newPlugin.SocketPath) 169 if err != nil { 170 return nil, errors.Wrapf(err, "cannot access plugin %s socket %q", name, newPlugin.SocketPath) 171 } 172 if stat.Mode()&os.ModeSocket == 0 { 173 return nil, errors.Wrapf(ErrNotPlugin, "volume %s path %q is not a unix socket", name, newPlugin.SocketPath) 174 } 175 176 if err := validatePlugin(newPlugin); err != nil { 177 return nil, err 178 } 179 180 return newPlugin, nil 181 } 182 183 func (p *VolumePlugin) getURI() string { 184 return "unix://" + p.SocketPath 185 } 186 187 // Verify the plugin is still available. 188 // TODO: Do we want to ping with an HTTP request? There's no ping endpoint so 189 // we'd need to hit Activate or Capabilities? 190 func (p *VolumePlugin) verifyReachable() error { 191 if _, err := os.Stat(p.SocketPath); err != nil { 192 if os.IsNotExist(err) { 193 pluginsLock.Lock() 194 defer pluginsLock.Unlock() 195 delete(plugins, p.Name) 196 return errors.Wrapf(ErrPluginRemoved, p.Name) 197 } 198 199 return errors.Wrapf(err, "error accessing plugin %s", p.Name) 200 } 201 return nil 202 } 203 204 // Send a request to the volume plugin for handling. 205 // Callers *MUST* close the response when they are done. 206 func (p *VolumePlugin) sendRequest(toJSON interface{}, hasBody bool, endpoint string) (*http.Response, error) { 207 var ( 208 reqJSON []byte 209 err error 210 ) 211 212 if hasBody { 213 reqJSON, err = json.Marshal(toJSON) 214 if err != nil { 215 return nil, errors.Wrapf(err, "error marshalling request JSON for volume plugin %s endpoint %s", p.Name, endpoint) 216 } 217 } 218 219 req, err := http.NewRequest("POST", "http://plugin"+endpoint, bytes.NewReader(reqJSON)) 220 if err != nil { 221 return nil, errors.Wrapf(err, "error making request to volume plugin %s endpoint %s", p.Name, endpoint) 222 } 223 224 req.Header.Set("Host", p.getURI()) 225 req.Header.Set("Content-Type", sdk.DefaultContentTypeV1_1) 226 227 resp, err := p.Client.Do(req) 228 if err != nil { 229 return nil, errors.Wrapf(err, "error sending request to volume plugin %s endpoint %s", p.Name, endpoint) 230 } 231 // We are *deliberately not closing* response here. It is the 232 // responsibility of the caller to do so after reading the response. 233 234 return resp, nil 235 } 236 237 // Turn an error response from a volume plugin into a well-formatted Go error. 238 func (p *VolumePlugin) makeErrorResponse(err, endpoint, volName string) error { 239 if err == "" { 240 err = "empty error from plugin" 241 } 242 if volName != "" { 243 return errors.Wrapf(errors.New(err), "error on %s on volume %s in volume plugin %s", endpoint, volName, p.Name) 244 } 245 return errors.Wrapf(errors.New(err), "error on %s in volume plugin %s", endpoint, p.Name) 246 } 247 248 // Handle error responses from plugin 249 func (p *VolumePlugin) handleErrorResponse(resp *http.Response, endpoint, volName string) error { 250 // The official plugin reference implementation uses HTTP 500 for 251 // errors, but I don't think we can guarantee all plugins do that. 252 // Let's interpret anything other than 200 as an error. 253 // If there isn't an error, don't even bother decoding the response. 254 if resp.StatusCode != 200 { 255 errResp, err := ioutil.ReadAll(resp.Body) 256 if err != nil { 257 return errors.Wrapf(err, "error reading response body from volume plugin %s", p.Name) 258 } 259 260 errStruct := new(volume.ErrorResponse) 261 if err := json.Unmarshal(errResp, errStruct); err != nil { 262 return errors.Wrapf(err, "error unmarshalling JSON response from volume plugin %s", p.Name) 263 } 264 265 return p.makeErrorResponse(errStruct.Err, endpoint, volName) 266 } 267 268 return nil 269 } 270 271 // CreateVolume creates a volume in the plugin. 272 func (p *VolumePlugin) CreateVolume(req *volume.CreateRequest) error { 273 if req == nil { 274 return errors.Wrapf(define.ErrInvalidArg, "must provide non-nil request to CreateVolume") 275 } 276 277 if err := p.verifyReachable(); err != nil { 278 return err 279 } 280 281 logrus.Infof("Creating volume %s using plugin %s", req.Name, p.Name) 282 283 resp, err := p.sendRequest(req, true, createPath) 284 if err != nil { 285 return err 286 } 287 defer resp.Body.Close() 288 289 return p.handleErrorResponse(resp, createPath, req.Name) 290 } 291 292 // ListVolumes lists volumes available in the plugin. 293 func (p *VolumePlugin) ListVolumes() ([]*volume.Volume, error) { 294 if err := p.verifyReachable(); err != nil { 295 return nil, err 296 } 297 298 logrus.Infof("Listing volumes using plugin %s", p.Name) 299 300 resp, err := p.sendRequest(nil, false, listPath) 301 if err != nil { 302 return nil, err 303 } 304 defer resp.Body.Close() 305 306 if err := p.handleErrorResponse(resp, listPath, ""); err != nil { 307 return nil, err 308 } 309 310 // TODO: Can probably unify response reading under a helper 311 volumeRespBytes, err := ioutil.ReadAll(resp.Body) 312 if err != nil { 313 return nil, errors.Wrapf(err, "error reading response body from volume plugin %s", p.Name) 314 } 315 316 volumeResp := new(volume.ListResponse) 317 if err := json.Unmarshal(volumeRespBytes, volumeResp); err != nil { 318 return nil, errors.Wrapf(err, "error unmarshalling volume plugin %s list response", p.Name) 319 } 320 321 return volumeResp.Volumes, nil 322 } 323 324 // GetVolume gets a single volume from the plugin. 325 func (p *VolumePlugin) GetVolume(req *volume.GetRequest) (*volume.Volume, error) { 326 if req == nil { 327 return nil, errors.Wrapf(define.ErrInvalidArg, "must provide non-nil request to GetVolume") 328 } 329 330 if err := p.verifyReachable(); err != nil { 331 return nil, err 332 } 333 334 logrus.Infof("Getting volume %s using plugin %s", req.Name, p.Name) 335 336 resp, err := p.sendRequest(req, true, getPath) 337 if err != nil { 338 return nil, err 339 } 340 defer resp.Body.Close() 341 342 if err := p.handleErrorResponse(resp, getPath, req.Name); err != nil { 343 return nil, err 344 } 345 346 getRespBytes, err := ioutil.ReadAll(resp.Body) 347 if err != nil { 348 return nil, errors.Wrapf(err, "error reading response body from volume plugin %s", p.Name) 349 } 350 351 getResp := new(volume.GetResponse) 352 if err := json.Unmarshal(getRespBytes, getResp); err != nil { 353 return nil, errors.Wrapf(err, "error unmarshalling volume plugin %s get response", p.Name) 354 } 355 356 return getResp.Volume, nil 357 } 358 359 // RemoveVolume removes a single volume from the plugin. 360 func (p *VolumePlugin) RemoveVolume(req *volume.RemoveRequest) error { 361 if req == nil { 362 return errors.Wrapf(define.ErrInvalidArg, "must provide non-nil request to RemoveVolume") 363 } 364 365 if err := p.verifyReachable(); err != nil { 366 return err 367 } 368 369 logrus.Infof("Removing volume %s using plugin %s", req.Name, p.Name) 370 371 resp, err := p.sendRequest(req, true, removePath) 372 if err != nil { 373 return err 374 } 375 defer resp.Body.Close() 376 377 return p.handleErrorResponse(resp, removePath, req.Name) 378 } 379 380 // GetVolumePath gets the path the given volume is mounted at. 381 func (p *VolumePlugin) GetVolumePath(req *volume.PathRequest) (string, error) { 382 if req == nil { 383 return "", errors.Wrapf(define.ErrInvalidArg, "must provide non-nil request to GetVolumePath") 384 } 385 386 if err := p.verifyReachable(); err != nil { 387 return "", err 388 } 389 390 logrus.Infof("Getting volume %s path using plugin %s", req.Name, p.Name) 391 392 resp, err := p.sendRequest(req, true, hostVirtualPath) 393 if err != nil { 394 return "", err 395 } 396 defer resp.Body.Close() 397 398 if err := p.handleErrorResponse(resp, hostVirtualPath, req.Name); err != nil { 399 return "", err 400 } 401 402 pathRespBytes, err := ioutil.ReadAll(resp.Body) 403 if err != nil { 404 return "", errors.Wrapf(err, "error reading response body from volume plugin %s", p.Name) 405 } 406 407 pathResp := new(volume.PathResponse) 408 if err := json.Unmarshal(pathRespBytes, pathResp); err != nil { 409 return "", errors.Wrapf(err, "error unmarshalling volume plugin %s path response", p.Name) 410 } 411 412 return pathResp.Mountpoint, nil 413 } 414 415 // MountVolume mounts the given volume. The ID argument is the ID of the 416 // mounting container, used for internal record-keeping by the plugin. Returns 417 // the path the volume has been mounted at. 418 func (p *VolumePlugin) MountVolume(req *volume.MountRequest) (string, error) { 419 if req == nil { 420 return "", errors.Wrapf(define.ErrInvalidArg, "must provide non-nil request to MountVolume") 421 } 422 423 if err := p.verifyReachable(); err != nil { 424 return "", err 425 } 426 427 logrus.Infof("Mounting volume %s using plugin %s for container %s", req.Name, p.Name, req.ID) 428 429 resp, err := p.sendRequest(req, true, mountPath) 430 if err != nil { 431 return "", err 432 } 433 defer resp.Body.Close() 434 435 if err := p.handleErrorResponse(resp, mountPath, req.Name); err != nil { 436 return "", err 437 } 438 439 mountRespBytes, err := ioutil.ReadAll(resp.Body) 440 if err != nil { 441 return "", errors.Wrapf(err, "error reading response body from volume plugin %s", p.Name) 442 } 443 444 mountResp := new(volume.MountResponse) 445 if err := json.Unmarshal(mountRespBytes, mountResp); err != nil { 446 return "", errors.Wrapf(err, "error unmarshalling volume plugin %s path response", p.Name) 447 } 448 449 return mountResp.Mountpoint, nil 450 } 451 452 // UnmountVolume unmounts the given volume. The ID argument is the ID of the 453 // container that is unmounting, used for internal record-keeping by the plugin. 454 func (p *VolumePlugin) UnmountVolume(req *volume.UnmountRequest) error { 455 if req == nil { 456 return errors.Wrapf(define.ErrInvalidArg, "must provide non-nil request to UnmountVolume") 457 } 458 459 if err := p.verifyReachable(); err != nil { 460 return err 461 } 462 463 logrus.Infof("Unmounting volume %s using plugin %s for container %s", req.Name, p.Name, req.ID) 464 465 resp, err := p.sendRequest(req, true, unmountPath) 466 if err != nil { 467 return err 468 } 469 defer resp.Body.Close() 470 471 return p.handleErrorResponse(resp, unmountPath, req.Name) 472 }