dotcloud/docker

View on GitHub
daemon/cluster/volumes.go

Summary

Maintainability
A
2 hrs
Test Coverage
package cluster // import "github.com/docker/docker/daemon/cluster"

import (
    "context"
    "fmt"

    volumetypes "github.com/docker/docker/api/types/volume"
    "github.com/docker/docker/daemon/cluster/convert"
    "github.com/docker/docker/errdefs"
    swarmapi "github.com/moby/swarmkit/v2/api"
    "google.golang.org/grpc"
)

// GetVolume returns a volume from the swarm cluster.
func (c *Cluster) GetVolume(nameOrID string) (volumetypes.Volume, error) {
    var volume *swarmapi.Volume

    if err := c.lockedManagerAction(func(ctx context.Context, state nodeState) error {
        v, err := getVolume(ctx, state.controlClient, nameOrID)
        if err != nil {
            return err
        }
        volume = v
        return nil
    }); err != nil {
        return volumetypes.Volume{}, err
    }
    return convert.VolumeFromGRPC(volume), nil
}

// GetVolumes returns all of the volumes matching the given options from a swarm cluster.
func (c *Cluster) GetVolumes(options volumetypes.ListOptions) ([]*volumetypes.Volume, error) {
    var volumes []*volumetypes.Volume
    if err := c.lockedManagerAction(func(ctx context.Context, state nodeState) error {
        r, err := state.controlClient.ListVolumes(
            ctx, &swarmapi.ListVolumesRequest{},
            grpc.MaxCallRecvMsgSize(defaultRecvSizeForListResponse),
        )
        if err != nil {
            return err
        }

        volumes = make([]*volumetypes.Volume, 0, len(r.Volumes))
        for _, volume := range r.Volumes {
            v := convert.VolumeFromGRPC(volume)
            volumes = append(volumes, &v)
        }

        return nil
    }); err != nil {
        return nil, err
    }

    return volumes, nil
}

// CreateVolume creates a new cluster volume in the swarm cluster.
//
// Returns the volume ID if creation is successful, or an error if not.
func (c *Cluster) CreateVolume(v volumetypes.CreateOptions) (*volumetypes.Volume, error) {
    var resp *swarmapi.CreateVolumeResponse
    if err := c.lockedManagerAction(func(ctx context.Context, state nodeState) error {
        volumeSpec := convert.VolumeCreateToGRPC(&v)

        r, err := state.controlClient.CreateVolume(
            ctx, &swarmapi.CreateVolumeRequest{Spec: volumeSpec},
        )
        if err != nil {
            return err
        }
        resp = r
        return nil
    }); err != nil {
        return nil, err
    }
    createdVol, err := c.GetVolume(resp.Volume.ID)
    if err != nil {
        // If there's a failure of some sort in this operation the user would
        // get a very unhelpful "not found" error on a create, which is not
        // very helpful at all. Instead, before returning the error, add some
        // context, and change this to a system-type error, because it's
        // nothing the user did wrong.
        return nil, errdefs.System(fmt.Errorf("unable to retrieve created volume: %w", err))
    }
    return &createdVol, nil
}

// RemoveVolume removes a volume from the swarm cluster.
func (c *Cluster) RemoveVolume(nameOrID string, force bool) error {
    return c.lockedManagerAction(func(ctx context.Context, state nodeState) error {
        volume, err := getVolume(ctx, state.controlClient, nameOrID)
        if err != nil {
            if force && errdefs.IsNotFound(err) {
                return nil
            }
            return err
        }

        _, err = state.controlClient.RemoveVolume(ctx, &swarmapi.RemoveVolumeRequest{
            VolumeID: volume.ID,
            Force:    force,
        })
        return err
    })
}

// UpdateVolume updates a volume in the swarm cluster.
func (c *Cluster) UpdateVolume(nameOrID string, version uint64, volume volumetypes.UpdateOptions) error {
    return c.lockedManagerAction(func(ctx context.Context, state nodeState) error {
        v, err := getVolume(ctx, state.controlClient, nameOrID)
        if err != nil {
            return err
        }

        // For now, the only thing we can update is availability. Instead of
        // converting the whole spec, just pluck out the availability if it has
        // been set.

        if volume.Spec != nil {
            switch volume.Spec.Availability {
            case volumetypes.AvailabilityActive:
                v.Spec.Availability = swarmapi.VolumeAvailabilityActive
            case volumetypes.AvailabilityPause:
                v.Spec.Availability = swarmapi.VolumeAvailabilityPause
            case volumetypes.AvailabilityDrain:
                v.Spec.Availability = swarmapi.VolumeAvailabilityDrain
            default:
                // if default empty value, change nothing.
            }
        }

        _, err = state.controlClient.UpdateVolume(ctx, &swarmapi.UpdateVolumeRequest{
            VolumeID: nameOrID,
            VolumeVersion: &swarmapi.Version{
                Index: version,
            },
            Spec: &v.Spec,
        })
        return err
    })
}