summaryrefslogtreecommitdiff
path: root/test/testvol
diff options
context:
space:
mode:
authorMatthew Heon <mheon@redhat.com>2021-01-11 16:14:07 -0500
committerMatthew Heon <mheon@redhat.com>2021-01-14 16:43:23 -0500
commitf781efd2dca4c1db54762c6edec2b915e48dd5d8 (patch)
treeb59fcfdfc29ff3979186116e23373c8d72f31169 /test/testvol
parentb53cb57680a6fd7b383636ac2d6cd71003532915 (diff)
downloadpodman-f781efd2dca4c1db54762c6edec2b915e48dd5d8.tar.gz
podman-f781efd2dca4c1db54762c6edec2b915e48dd5d8.tar.bz2
podman-f781efd2dca4c1db54762c6edec2b915e48dd5d8.zip
Add tests for volume plugins
This involves a new test binary (a basic implementation of the volume plugin protocol) and a new image on quay.io (Containerfile to produce it and all sources located in this commit). The image is used to run a containerized plugin we can test against. Signed-off-by: Matthew Heon <mheon@redhat.com>
Diffstat (limited to 'test/testvol')
-rw-r--r--test/testvol/main.go309
1 files changed, 309 insertions, 0 deletions
diff --git a/test/testvol/main.go b/test/testvol/main.go
new file mode 100644
index 000000000..14f253aa7
--- /dev/null
+++ b/test/testvol/main.go
@@ -0,0 +1,309 @@
+package main
+
+import (
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "sync"
+ "time"
+
+ "github.com/docker/go-plugins-helpers/volume"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "github.com/spf13/cobra"
+)
+
+var rootCmd = &cobra.Command{
+ Use: "testvol",
+ Short: "testvol - volume plugin for Podman",
+ Long: `Creates simple directory volumes using the Volume Plugin API for testing volume plugin functionality`,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return startServer(config.sockName)
+ },
+ PersistentPreRunE: before,
+}
+
+// Configuration for the volume plugin
+type cliConfig struct {
+ logLevel string
+ sockName string
+ path string
+}
+
+// Default configuration is stored here. Will be overwritten by flags.
+var config cliConfig = cliConfig{
+ logLevel: "error",
+ sockName: "test-volume-plugin",
+}
+
+func init() {
+ rootCmd.Flags().StringVar(&config.sockName, "sock-name", config.sockName, "Name of unix socket for plugin")
+ rootCmd.Flags().StringVar(&config.path, "path", "", "Path to initialize state and mount points")
+ rootCmd.PersistentFlags().StringVar(&config.logLevel, "log-level", config.logLevel, "Log messages including and over the specified level: debug, info, warn, error, fatal, panic")
+}
+
+func before(cmd *cobra.Command, args []string) error {
+ if config.logLevel == "" {
+ config.logLevel = "error"
+ }
+
+ level, err := logrus.ParseLevel(config.logLevel)
+ if err != nil {
+ return err
+ }
+
+ logrus.SetLevel(level)
+
+ return nil
+}
+
+func main() {
+ if err := rootCmd.Execute(); err != nil {
+ logrus.Errorf("Error running volume plugin: %v", err)
+ os.Exit(1)
+ }
+
+ os.Exit(0)
+}
+
+// startServer runs the HTTP server and responds to requests
+func startServer(socketPath string) error {
+ logrus.Debugf("Starting server...")
+
+ if config.path == "" {
+ path, err := ioutil.TempDir("", "test_volume_plugin")
+ if err != nil {
+ return errors.Wrapf(err, "error getting directory for plugin")
+ }
+ config.path = path
+ } else {
+ pathStat, err := os.Stat(config.path)
+ if err != nil {
+ return errors.Wrapf(err, "unable to access requested plugin state directory")
+ }
+ if !pathStat.IsDir() {
+ return errors.Errorf("cannot use %v as plugin state dir as it is not a directory", config.path)
+ }
+ }
+
+ handle, err := makeDirDriver(config.path)
+ if err != nil {
+ return errors.Wrapf(err, "error making volume driver")
+ }
+ logrus.Infof("Using %s for volume path", config.path)
+
+ server := volume.NewHandler(handle)
+ if err := server.ServeUnix(socketPath, 0); err != nil {
+ return errors.Wrapf(err, "error starting server")
+ }
+ return nil
+}
+
+// DirDriver is a trivial volume driver implementation.
+// the volumes field maps name to volume
+type DirDriver struct {
+ lock sync.Mutex
+ volumesPath string
+ volumes map[string]*dirVol
+}
+
+type dirVol struct {
+ name string
+ path string
+ options map[string]string
+ mounts map[string]bool
+ createTime time.Time
+}
+
+// Make a new DirDriver.
+func makeDirDriver(path string) (volume.Driver, error) {
+ drv := new(DirDriver)
+ drv.volumesPath = path
+ drv.volumes = make(map[string]*dirVol)
+
+ return drv, nil
+}
+
+// Capabilities returns the capabilities of the driver.
+func (d *DirDriver) Capabilities() *volume.CapabilitiesResponse {
+ logrus.Infof("Hit Capabilities() endpoint")
+
+ return &volume.CapabilitiesResponse{
+ volume.Capability{
+ "local",
+ },
+ }
+}
+
+// Create creates a volume.
+func (d *DirDriver) Create(opts *volume.CreateRequest) error {
+ d.lock.Lock()
+ defer d.lock.Unlock()
+
+ logrus.Infof("Hit Create() endpoint")
+
+ if _, exists := d.volumes[opts.Name]; exists {
+ return errors.Errorf("volume with name %s already exists", opts.Name)
+ }
+
+ newVol := new(dirVol)
+ newVol.name = opts.Name
+ newVol.mounts = make(map[string]bool)
+ newVol.options = make(map[string]string)
+ newVol.createTime = time.Now()
+ for k, v := range opts.Options {
+ newVol.options[k] = v
+ }
+
+ volPath := filepath.Join(d.volumesPath, opts.Name)
+ if err := os.Mkdir(volPath, 0755); err != nil {
+ return errors.Wrapf(err, "error making volume directory")
+ }
+ newVol.path = volPath
+
+ d.volumes[opts.Name] = newVol
+
+ logrus.Debugf("Made volume with name %s and path %s", newVol.name, newVol.path)
+
+ return nil
+}
+
+// List lists all volumes available.
+func (d *DirDriver) List() (*volume.ListResponse, error) {
+ d.lock.Lock()
+ defer d.lock.Unlock()
+
+ logrus.Infof("Hit List() endpoint")
+
+ vols := new(volume.ListResponse)
+ vols.Volumes = []*volume.Volume{}
+
+ for _, vol := range d.volumes {
+ newVol := new(volume.Volume)
+ newVol.Name = vol.name
+ newVol.Mountpoint = vol.path
+ newVol.CreatedAt = vol.createTime.String()
+ vols.Volumes = append(vols.Volumes, newVol)
+ logrus.Debugf("Adding volume %s to list response", newVol.Name)
+ }
+
+ return vols, nil
+}
+
+// Get retrieves a single volume.
+func (d *DirDriver) Get(req *volume.GetRequest) (*volume.GetResponse, error) {
+ d.lock.Lock()
+ defer d.lock.Unlock()
+
+ logrus.Infof("Hit Get() endpoint")
+
+ vol, exists := d.volumes[req.Name]
+ if !exists {
+ logrus.Debugf("Did not find volume %s", req.Name)
+ return nil, errors.Errorf("no volume with name %s found", req.Name)
+ }
+
+ logrus.Debugf("Found volume %s", req.Name)
+
+ resp := new(volume.GetResponse)
+ resp.Volume = new(volume.Volume)
+ resp.Volume.Name = vol.name
+ resp.Volume.Mountpoint = vol.path
+ resp.Volume.CreatedAt = vol.createTime.String()
+
+ return resp, nil
+}
+
+// Remove removes a single volume.
+func (d *DirDriver) Remove(req *volume.RemoveRequest) error {
+ d.lock.Lock()
+ defer d.lock.Unlock()
+
+ logrus.Infof("Hit Remove() endpoint")
+
+ vol, exists := d.volumes[req.Name]
+ if !exists {
+ logrus.Debugf("Did not find volume %s", req.Name)
+ return errors.Errorf("no volume with name %s found")
+ }
+ logrus.Debugf("Found volume %s", req.Name)
+
+ if len(vol.mounts) > 0 {
+ logrus.Debugf("Cannot remove %s, is mounted", req.Name)
+ return errors.Errorf("volume %s is mounted and cannot be removed")
+ }
+
+ delete(d.volumes, req.Name)
+
+ if err := os.RemoveAll(vol.path); err != nil {
+ return errors.Wrapf(err, "error removing mountpoint of volume %s", req.Name)
+ }
+
+ logrus.Debugf("Removed volume %s", req.Name)
+
+ return nil
+}
+
+// Path returns the path a single volume is mounted at.
+func (d *DirDriver) Path(req *volume.PathRequest) (*volume.PathResponse, error) {
+ d.lock.Lock()
+ defer d.lock.Unlock()
+
+ logrus.Infof("Hit Path() endpoint")
+
+ // TODO: Should we return error if not mounted?
+
+ vol, exists := d.volumes[req.Name]
+ if !exists {
+ logrus.Debugf("Cannot locate volume %s", req.Name)
+ return nil, errors.Errorf("no volume with name %s found", req.Name)
+ }
+
+ return &volume.PathResponse{
+ vol.path,
+ }, nil
+}
+
+// Mount mounts the volume.
+func (d *DirDriver) Mount(req *volume.MountRequest) (*volume.MountResponse, error) {
+ d.lock.Lock()
+ defer d.lock.Unlock()
+
+ logrus.Infof("Hit Mount() endpoint")
+
+ vol, exists := d.volumes[req.Name]
+ if !exists {
+ logrus.Debugf("Cannot locate volume %s", req.Name)
+ return nil, errors.Errorf("no volume with name %s found", req.Name)
+ }
+
+ vol.mounts[req.ID] = true
+
+ return &volume.MountResponse{
+ vol.path,
+ }, nil
+}
+
+// Unmount unmounts the volume.
+func (d *DirDriver) Unmount(req *volume.UnmountRequest) error {
+ d.lock.Lock()
+ defer d.lock.Unlock()
+
+ logrus.Infof("Hit Unmount() endpoint")
+
+ vol, exists := d.volumes[req.Name]
+ if !exists {
+ logrus.Debugf("Cannot locate volume %s", req.Name)
+ return errors.Errorf("no volume with name %s found", req.Name)
+ }
+
+ mount := vol.mounts[req.ID]
+ if !mount {
+ logrus.Debugf("Volume %s is not mounted by %s", req.Name, req.ID)
+ return errors.Errorf("volume %s is not mounted by %s", req.Name, req.ID)
+ }
+
+ delete(vol.mounts, req.ID)
+
+ return nil
+}