moby/daemon/cluster/convert/volume.go
Drew Erny 240a9fcb83
Add Swarm cluster volume supports
Adds code to support Cluster Volumes in Swarm using CSI drivers.

Signed-off-by: Drew Erny <derny@mirantis.com>
2022-05-13 00:55:44 +02:00

311 lines
9.1 KiB
Go

package convert // import "github.com/docker/docker/daemon/cluster/convert"
import (
volumetypes "github.com/docker/docker/api/types/volume"
gogotypes "github.com/gogo/protobuf/types"
swarmapi "github.com/moby/swarmkit/v2/api"
)
// VolumeFromGRPC converts a swarmkit api Volume object to a docker api Volume
// object
func VolumeFromGRPC(v *swarmapi.Volume) volumetypes.Volume {
clusterVolumeSpec := volumetypes.ClusterVolumeSpec{
Group: v.Spec.Group,
AccessMode: accessModeFromGRPC(v.Spec.AccessMode),
AccessibilityRequirements: topologyRequirementFromGRPC(v.Spec.AccessibilityRequirements),
CapacityRange: capacityRangeFromGRPC(v.Spec.CapacityRange),
Secrets: volumeSecretsFromGRPC(v.Spec.Secrets),
Availability: volumeAvailabilityFromGRPC(v.Spec.Availability),
}
clusterVolume := &volumetypes.ClusterVolume{
ID: v.ID,
Spec: clusterVolumeSpec,
PublishStatus: volumePublishStatusFromGRPC(v.PublishStatus),
Info: volumeInfoFromGRPC(v.VolumeInfo),
}
clusterVolume.Version.Index = v.Meta.Version.Index
clusterVolume.CreatedAt, _ = gogotypes.TimestampFromProto(v.Meta.CreatedAt)
clusterVolume.UpdatedAt, _ = gogotypes.TimestampFromProto(v.Meta.UpdatedAt)
return volumetypes.Volume{
ClusterVolume: clusterVolume,
CreatedAt: clusterVolume.CreatedAt.String(),
Driver: v.Spec.Driver.Name,
Labels: v.Spec.Annotations.Labels,
Name: v.Spec.Annotations.Name,
Options: v.Spec.Driver.Options,
Scope: "global",
}
}
func volumeSpecToGRPC(spec volumetypes.ClusterVolumeSpec) *swarmapi.VolumeSpec {
swarmSpec := &swarmapi.VolumeSpec{
Group: spec.Group,
}
if spec.AccessMode != nil {
swarmSpec.AccessMode = &swarmapi.VolumeAccessMode{}
switch spec.AccessMode.Scope {
case volumetypes.ScopeSingleNode:
swarmSpec.AccessMode.Scope = swarmapi.VolumeScopeSingleNode
case volumetypes.ScopeMultiNode:
swarmSpec.AccessMode.Scope = swarmapi.VolumeScopeMultiNode
}
switch spec.AccessMode.Sharing {
case volumetypes.SharingNone:
swarmSpec.AccessMode.Sharing = swarmapi.VolumeSharingNone
case volumetypes.SharingReadOnly:
swarmSpec.AccessMode.Sharing = swarmapi.VolumeSharingReadOnly
case volumetypes.SharingOneWriter:
swarmSpec.AccessMode.Sharing = swarmapi.VolumeSharingOneWriter
case volumetypes.SharingAll:
swarmSpec.AccessMode.Sharing = swarmapi.VolumeSharingAll
}
if spec.AccessMode.BlockVolume != nil {
swarmSpec.AccessMode.AccessType = &swarmapi.VolumeAccessMode_Block{
Block: &swarmapi.VolumeAccessMode_BlockVolume{},
}
}
if spec.AccessMode.MountVolume != nil {
swarmSpec.AccessMode.AccessType = &swarmapi.VolumeAccessMode_Mount{
Mount: &swarmapi.VolumeAccessMode_MountVolume{
FsType: spec.AccessMode.MountVolume.FsType,
MountFlags: spec.AccessMode.MountVolume.MountFlags,
},
}
}
}
for _, secret := range spec.Secrets {
swarmSpec.Secrets = append(swarmSpec.Secrets, &swarmapi.VolumeSecret{
Key: secret.Key,
Secret: secret.Secret,
})
}
if spec.AccessibilityRequirements != nil {
swarmSpec.AccessibilityRequirements = &swarmapi.TopologyRequirement{}
for _, top := range spec.AccessibilityRequirements.Requisite {
swarmSpec.AccessibilityRequirements.Requisite = append(
swarmSpec.AccessibilityRequirements.Requisite,
&swarmapi.Topology{
Segments: top.Segments,
},
)
}
for _, top := range spec.AccessibilityRequirements.Preferred {
swarmSpec.AccessibilityRequirements.Preferred = append(
swarmSpec.AccessibilityRequirements.Preferred,
&swarmapi.Topology{
Segments: top.Segments,
},
)
}
}
if spec.CapacityRange != nil {
swarmSpec.CapacityRange = &swarmapi.CapacityRange{
RequiredBytes: spec.CapacityRange.RequiredBytes,
LimitBytes: spec.CapacityRange.LimitBytes,
}
}
// availability is not a pointer, it is a value. if the user does not
// specify an availability, it will be inferred as the 0-value, which is
// "active".
switch spec.Availability {
case volumetypes.AvailabilityActive:
swarmSpec.Availability = swarmapi.VolumeAvailabilityActive
case volumetypes.AvailabilityPause:
swarmSpec.Availability = swarmapi.VolumeAvailabilityPause
case volumetypes.AvailabilityDrain:
swarmSpec.Availability = swarmapi.VolumeAvailabilityDrain
}
return swarmSpec
}
// VolumeCreateToGRPC takes a VolumeCreateBody and outputs the matching
// swarmapi VolumeSpec.
func VolumeCreateToGRPC(volume *volumetypes.CreateOptions) *swarmapi.VolumeSpec {
var swarmSpec *swarmapi.VolumeSpec
if volume != nil && volume.ClusterVolumeSpec != nil {
swarmSpec = volumeSpecToGRPC(*volume.ClusterVolumeSpec)
} else {
swarmSpec = &swarmapi.VolumeSpec{}
}
swarmSpec.Annotations = swarmapi.Annotations{
Name: volume.Name,
Labels: volume.Labels,
}
swarmSpec.Driver = &swarmapi.Driver{
Name: volume.Driver,
Options: volume.DriverOpts,
}
return swarmSpec
}
func volumeInfoFromGRPC(info *swarmapi.VolumeInfo) *volumetypes.Info {
if info == nil {
return nil
}
var accessibleTopology []volumetypes.Topology
if info.AccessibleTopology != nil {
accessibleTopology = make([]volumetypes.Topology, len(info.AccessibleTopology))
for i, top := range info.AccessibleTopology {
accessibleTopology[i] = topologyFromGRPC(top)
}
}
return &volumetypes.Info{
CapacityBytes: info.CapacityBytes,
VolumeContext: info.VolumeContext,
VolumeID: info.VolumeID,
AccessibleTopology: accessibleTopology,
}
}
func volumePublishStatusFromGRPC(publishStatus []*swarmapi.VolumePublishStatus) []*volumetypes.PublishStatus {
if publishStatus == nil {
return nil
}
vps := make([]*volumetypes.PublishStatus, len(publishStatus))
for i, status := range publishStatus {
var state volumetypes.PublishState
switch status.State {
case swarmapi.VolumePublishStatus_PENDING_PUBLISH:
state = volumetypes.StatePending
case swarmapi.VolumePublishStatus_PUBLISHED:
state = volumetypes.StatePublished
case swarmapi.VolumePublishStatus_PENDING_NODE_UNPUBLISH:
state = volumetypes.StatePendingNodeUnpublish
case swarmapi.VolumePublishStatus_PENDING_UNPUBLISH:
state = volumetypes.StatePendingUnpublish
}
vps[i] = &volumetypes.PublishStatus{
NodeID: status.NodeID,
State: state,
PublishContext: status.PublishContext,
}
}
return vps
}
func accessModeFromGRPC(accessMode *swarmapi.VolumeAccessMode) *volumetypes.AccessMode {
if accessMode == nil {
return nil
}
convertedAccessMode := &volumetypes.AccessMode{}
switch accessMode.Scope {
case swarmapi.VolumeScopeSingleNode:
convertedAccessMode.Scope = volumetypes.ScopeSingleNode
case swarmapi.VolumeScopeMultiNode:
convertedAccessMode.Scope = volumetypes.ScopeMultiNode
}
switch accessMode.Sharing {
case swarmapi.VolumeSharingNone:
convertedAccessMode.Sharing = volumetypes.SharingNone
case swarmapi.VolumeSharingReadOnly:
convertedAccessMode.Sharing = volumetypes.SharingReadOnly
case swarmapi.VolumeSharingOneWriter:
convertedAccessMode.Sharing = volumetypes.SharingOneWriter
case swarmapi.VolumeSharingAll:
convertedAccessMode.Sharing = volumetypes.SharingAll
}
if block := accessMode.GetBlock(); block != nil {
convertedAccessMode.BlockVolume = &volumetypes.TypeBlock{}
}
if mount := accessMode.GetMount(); mount != nil {
convertedAccessMode.MountVolume = &volumetypes.TypeMount{
FsType: mount.FsType,
MountFlags: mount.MountFlags,
}
}
return convertedAccessMode
}
func volumeSecretsFromGRPC(secrets []*swarmapi.VolumeSecret) []volumetypes.Secret {
if secrets == nil {
return nil
}
convertedSecrets := make([]volumetypes.Secret, len(secrets))
for i, secret := range secrets {
convertedSecrets[i] = volumetypes.Secret{
Key: secret.Key,
Secret: secret.Secret,
}
}
return convertedSecrets
}
func topologyRequirementFromGRPC(top *swarmapi.TopologyRequirement) *volumetypes.TopologyRequirement {
if top == nil {
return nil
}
convertedTop := &volumetypes.TopologyRequirement{}
if top.Requisite != nil {
convertedTop.Requisite = make([]volumetypes.Topology, len(top.Requisite))
for i, req := range top.Requisite {
convertedTop.Requisite[i] = topologyFromGRPC(req)
}
}
if top.Preferred != nil {
convertedTop.Preferred = make([]volumetypes.Topology, len(top.Preferred))
for i, pref := range top.Preferred {
convertedTop.Preferred[i] = topologyFromGRPC(pref)
}
}
return convertedTop
}
func topologyFromGRPC(top *swarmapi.Topology) volumetypes.Topology {
if top == nil {
return volumetypes.Topology{}
}
return volumetypes.Topology{
Segments: top.Segments,
}
}
func capacityRangeFromGRPC(capacity *swarmapi.CapacityRange) *volumetypes.CapacityRange {
if capacity == nil {
return nil
}
return &volumetypes.CapacityRange{
RequiredBytes: capacity.RequiredBytes,
LimitBytes: capacity.LimitBytes,
}
}
func volumeAvailabilityFromGRPC(availability swarmapi.VolumeSpec_VolumeAvailability) volumetypes.Availability {
switch availability {
case swarmapi.VolumeAvailabilityActive:
return volumetypes.AvailabilityActive
case swarmapi.VolumeAvailabilityPause:
return volumetypes.AvailabilityPause
}
return volumetypes.AvailabilityDrain
}