Skip to content

Commit

Permalink
Merge pull request #3662 from dperny/cluster-volumes-compose
Browse files Browse the repository at this point in the history
Add compose support for cluster volumes
  • Loading branch information
sam-thibault committed Nov 2, 2022
2 parents a3e0477 + 02e7826 commit 3dfef76
Show file tree
Hide file tree
Showing 7 changed files with 411 additions and 16 deletions.
58 changes: 57 additions & 1 deletion cli/compose/convert/volume.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package convert

import (
"strings"

composetypes "github.com/docker/cli/cli/compose/types"
"github.com/docker/docker/api/types/mount"
"github.com/pkg/errors"
Expand Down Expand Up @@ -45,6 +47,9 @@ func handleVolumeToMount(
if volume.Bind != nil {
return mount.Mount{}, errors.New("bind options are incompatible with type volume")
}
if volume.Cluster != nil {
return mount.Mount{}, errors.New("cluster options are incompatible with type volume")
}
// Anonymous volumes
if volume.Source == "" {
return result, nil
Expand Down Expand Up @@ -94,6 +99,9 @@ func handleBindToMount(volume composetypes.ServiceVolumeConfig) (mount.Mount, er
if volume.Tmpfs != nil {
return mount.Mount{}, errors.New("tmpfs options are incompatible with type bind")
}
if volume.Cluster != nil {
return mount.Mount{}, errors.New("cluster options are incompatible with type bind")
}
if volume.Bind != nil {
result.BindOptions = &mount.BindOptions{
Propagation: mount.Propagation(volume.Bind.Propagation),
Expand All @@ -114,6 +122,9 @@ func handleTmpfsToMount(volume composetypes.ServiceVolumeConfig) (mount.Mount, e
if volume.Volume != nil {
return mount.Mount{}, errors.New("volume options are incompatible with type tmpfs")
}
if volume.Cluster != nil {
return mount.Mount{}, errors.New("cluster options are incompatible with type tmpfs")
}
if volume.Tmpfs != nil {
result.TmpfsOptions = &mount.TmpfsOptions{
SizeBytes: volume.Tmpfs.Size,
Expand Down Expand Up @@ -142,6 +153,49 @@ func handleNpipeToMount(volume composetypes.ServiceVolumeConfig) (mount.Mount, e
return result, nil
}

func handleClusterToMount(
volume composetypes.ServiceVolumeConfig,
stackVolumes volumes,
namespace Namespace,
) (mount.Mount, error) {
if volume.Source == "" {
return mount.Mount{}, errors.New("invalid cluster source, source cannot be empty")
}
if volume.Tmpfs != nil {
return mount.Mount{}, errors.New("tmpfs options are incompatible with type cluster")
}
if volume.Bind != nil {
return mount.Mount{}, errors.New("bind options are incompatible with type cluster")
}
if volume.Volume != nil {
return mount.Mount{}, errors.New("volume options are incompatible with type cluster")
}

result := createMountFromVolume(volume)
result.ClusterOptions = &mount.ClusterOptions{}

if !strings.HasPrefix(volume.Source, "group:") {
// if the volume is a cluster volume and the source is a volumegroup, we
// will ignore checking to see if such a volume is defined. the volume
// group isn't namespaced, and there's no simple way to indicate that
// external volumes with a given group exist.
stackVolume, exists := stackVolumes[volume.Source]
if !exists {
return mount.Mount{}, errors.Errorf("undefined volume %q", volume.Source)
}

// if the volume is not specified with a group source, we may namespace
// the name, if one is not otherwise specified.
if stackVolume.Name != "" {
result.Source = stackVolume.Name
} else {
result.Source = namespace.Scope(volume.Source)
}
}

return result, nil
}

func convertVolumeToMount(
volume composetypes.ServiceVolumeConfig,
stackVolumes volumes,
Expand All @@ -156,6 +210,8 @@ func convertVolumeToMount(
return handleTmpfsToMount(volume)
case "npipe":
return handleNpipeToMount(volume)
case "cluster":
return handleClusterToMount(volume, stackVolumes, namespace)
}
return mount.Mount{}, errors.New("volume type must be volume, bind, tmpfs or npipe")
return mount.Mount{}, errors.New("volume type must be volume, bind, tmpfs, npipe, or cluster")
}
70 changes: 69 additions & 1 deletion cli/compose/convert/volume_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func TestConvertVolumeToMountUnapprovedType(t *testing.T) {
Target: "/foo/bar",
}
_, err := convertVolumeToMount(config, volumes{}, NewNamespace("foo"))
assert.Error(t, err, "volume type must be volume, bind, tmpfs or npipe")
assert.Error(t, err, "volume type must be volume, bind, tmpfs, npipe, or cluster")
}

func TestConvertVolumeToMountConflictingOptionsBindInVolume(t *testing.T) {
Expand Down Expand Up @@ -359,3 +359,71 @@ func TestConvertVolumeToMountAnonymousNpipe(t *testing.T) {
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(expected, mount))
}

func TestConvertVolumeMountClusterName(t *testing.T) {
stackVolumes := volumes{
"my-csi": composetypes.VolumeConfig{
Driver: "mycsidriver",
Spec: &composetypes.ClusterVolumeSpec{
Group: "mygroup",
AccessMode: &composetypes.AccessMode{
Scope: "single",
Sharing: "none",
BlockVolume: &composetypes.BlockVolume{},
},
Availability: "active",
},
},
}

config := composetypes.ServiceVolumeConfig{
Type: "cluster",
Source: "my-csi",
Target: "/srv",
}

expected := mount.Mount{
Type: mount.TypeCluster,
Source: "foo_my-csi",
Target: "/srv",
ClusterOptions: &mount.ClusterOptions{},
}

mount, err := convertVolumeToMount(config, stackVolumes, NewNamespace("foo"))
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(expected, mount))
}

func TestConvertVolumeMountClusterGroup(t *testing.T) {
stackVolumes := volumes{
"my-csi": composetypes.VolumeConfig{
Driver: "mycsidriver",
Spec: &composetypes.ClusterVolumeSpec{
Group: "mygroup",
AccessMode: &composetypes.AccessMode{
Scope: "single",
Sharing: "none",
BlockVolume: &composetypes.BlockVolume{},
},
Availability: "active",
},
},
}

config := composetypes.ServiceVolumeConfig{
Type: "cluster",
Source: "group:mygroup",
Target: "/srv",
}

expected := mount.Mount{
Type: mount.TypeCluster,
Source: "group:mygroup",
Target: "/srv",
ClusterOptions: &mount.ClusterOptions{},
}

mount, err := convertVolumeToMount(config, stackVolumes, NewNamespace("foo"))
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(expected, mount))
}
35 changes: 34 additions & 1 deletion cli/compose/loader/full-example.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: "3.9"
version: "3.10"

services:
foo:
Expand Down Expand Up @@ -287,6 +287,9 @@ services:
target: /opt
tmpfs:
size: 10000
- type: cluster
source: group:mygroup
target: /srv

working_dir: /code
x-bar: baz
Expand Down Expand Up @@ -379,6 +382,36 @@ volumes:
x-bar: baz
x-foo: bar

cluster-volume:
driver: my-csi-driver
x-cluster-spec:
group: mygroup
access_mode:
scope: single
sharing: none
block_volume: {}
accessibility_requirements:
requisite:
- segments:
- region=R1
- zone=Z1
- segments:
region: R1
zone: Z2
preferred:
- segments:
region: R1
zone: Z1
capacity_range:
required_bytes: 1G
limit_bytes: 8G
secrets:
- key: mycsisecret
secret: secret1
- key: mycsisecret2
secret: secret4
availability: active

configs:
config1:
file: ./config_data
Expand Down
Loading

0 comments on commit 3dfef76

Please sign in to comment.