// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package structs import ( "testing" "github.com/hashicorp/nomad/ci" "github.com/shoenig/test/must" "github.com/stretchr/testify/require" ) func TestVolumeRequest_Validate(t *testing.T) { ci.Parallel(t) testCases := []struct { name string expected []string canariesCount int taskGroupCount int req *VolumeRequest }{ { name: "host volume with empty source", expected: []string{"volume has an empty source"}, req: &VolumeRequest{ Type: VolumeTypeHost, }, }, { name: "host volume with CSI volume config", expected: []string{ "host volumes cannot have an access mode", "host volumes cannot have an attachment mode", "host volumes cannot have mount options", "volume cannot be per_alloc for system or sysbatch jobs", "volume cannot be per_alloc when canaries are in use", }, canariesCount: 1, req: &VolumeRequest{ Type: VolumeTypeHost, ReadOnly: false, AccessMode: CSIVolumeAccessModeSingleNodeReader, AttachmentMode: CSIVolumeAttachmentModeBlockDevice, MountOptions: &CSIMountOptions{ FSType: "ext4", MountFlags: []string{"ro"}, }, PerAlloc: true, }, }, { name: "CSI volume multi-reader-single-writer access mode", expected: []string{ "volume with multi-node-single-writer access mode allows only one writer", }, taskGroupCount: 2, req: &VolumeRequest{ Type: VolumeTypeCSI, AccessMode: CSIVolumeAccessModeMultiNodeSingleWriter, }, }, { name: "CSI volume single reader access mode", expected: []string{ "volume with single-node-reader-only access mode allows only one reader", }, taskGroupCount: 2, req: &VolumeRequest{ Type: VolumeTypeCSI, AccessMode: CSIVolumeAccessModeSingleNodeReader, ReadOnly: true, }, }, { name: "CSI volume per-alloc with canaries", expected: []string{ "volume cannot be per_alloc for system or sysbatch jobs", "volume cannot be per_alloc when canaries are in use", }, canariesCount: 1, req: &VolumeRequest{ Type: VolumeTypeCSI, PerAlloc: true, }, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { err := tc.req.Validate(JobTypeSystem, tc.taskGroupCount, tc.canariesCount) for _, expected := range tc.expected { require.Contains(t, err.Error(), expected) } }) } } func TestVolumeRequest_Equal(t *testing.T) { ci.Parallel(t) must.Equal[*VolumeRequest](t, nil, nil) must.NotEqual[*VolumeRequest](t, nil, new(VolumeRequest)) must.StructEqual(t, &VolumeRequest{ Name: "name", Type: "type", Source: "source", ReadOnly: true, AccessMode: "access", AttachmentMode: "attachment", MountOptions: &CSIMountOptions{ FSType: "fs1", MountFlags: []string{"flag1"}, }, PerAlloc: true, }, []must.Tweak[*VolumeRequest]{{ Field: "Name", Apply: func(vr *VolumeRequest) { vr.Name = "name2" }, }, { Field: "Type", Apply: func(vr *VolumeRequest) { vr.Type = "type2" }, }, { Field: "Source", Apply: func(vr *VolumeRequest) { vr.Source = "source2" }, }, { Field: "ReadOnly", Apply: func(vr *VolumeRequest) { vr.ReadOnly = false }, }, { Field: "AccessMode", Apply: func(vr *VolumeRequest) { vr.AccessMode = "access2" }, }, { Field: "AttachmentMode", Apply: func(vr *VolumeRequest) { vr.AttachmentMode = "attachment2" }, }, { Field: "MountOptions", Apply: func(vr *VolumeRequest) { vr.MountOptions = nil }, }, { Field: "PerAlloc", Apply: func(vr *VolumeRequest) { vr.PerAlloc = false }, }}) } func TestVolumeMount_Equal(t *testing.T) { ci.Parallel(t) must.Equal[*VolumeMount](t, nil, nil) must.NotEqual[*VolumeMount](t, nil, new(VolumeMount)) must.StructEqual(t, &VolumeMount{ Volume: "volume", Destination: "destination", ReadOnly: true, PropagationMode: "mode", }, []must.Tweak[*VolumeMount]{{ Field: "Volume", Apply: func(vm *VolumeMount) { vm.Volume = "vol2" }, }, { Field: "Destination", Apply: func(vm *VolumeMount) { vm.Destination = "dest2" }, }, { Field: "ReadOnly", Apply: func(vm *VolumeMount) { vm.ReadOnly = false }, }, { Field: "PropogationMode", Apply: func(vm *VolumeMount) { vm.PropagationMode = "mode2" }, }}) }