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  }