open-nomad/nomad/structs/csi_test.go

396 lines
10 KiB
Go

package structs
import (
"reflect"
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestCSIVolumeClaim(t *testing.T) {
vol := NewCSIVolume("", 0)
vol.AccessMode = CSIVolumeAccessModeMultiNodeSingleWriter
vol.Schedulable = true
alloc := &Allocation{ID: "a1", Namespace: "n", JobID: "j"}
claim := &CSIVolumeClaim{
AllocationID: alloc.ID,
NodeID: "foo",
Mode: CSIVolumeClaimRead,
}
require.NoError(t, vol.ClaimRead(claim, alloc))
require.True(t, vol.ReadSchedulable())
require.True(t, vol.WriteSchedulable())
require.NoError(t, vol.ClaimRead(claim, alloc))
claim.Mode = CSIVolumeClaimWrite
require.NoError(t, vol.ClaimWrite(claim, alloc))
require.True(t, vol.ReadSchedulable())
require.False(t, vol.WriteFreeClaims())
vol.ClaimRelease(claim)
require.True(t, vol.ReadSchedulable())
require.False(t, vol.WriteFreeClaims())
claim.State = CSIVolumeClaimStateReadyToFree
vol.ClaimRelease(claim)
require.True(t, vol.ReadSchedulable())
require.True(t, vol.WriteFreeClaims())
vol.AccessMode = CSIVolumeAccessModeMultiNodeMultiWriter
require.NoError(t, vol.ClaimWrite(claim, alloc))
require.NoError(t, vol.ClaimWrite(claim, alloc))
require.True(t, vol.WriteFreeClaims())
}
func TestVolume_Copy(t *testing.T) {
a1 := MockAlloc()
a2 := MockAlloc()
a3 := MockAlloc()
c1 := &CSIVolumeClaim{
AllocationID: a1.ID,
NodeID: a1.NodeID,
ExternalNodeID: "c1",
Mode: CSIVolumeClaimRead,
State: CSIVolumeClaimStateTaken,
}
c2 := &CSIVolumeClaim{
AllocationID: a2.ID,
NodeID: a2.NodeID,
ExternalNodeID: "c2",
Mode: CSIVolumeClaimRead,
State: CSIVolumeClaimStateNodeDetached,
}
c3 := &CSIVolumeClaim{
AllocationID: a3.ID,
NodeID: a3.NodeID,
ExternalNodeID: "c3",
Mode: CSIVolumeClaimWrite,
State: CSIVolumeClaimStateTaken,
}
v1 := &CSIVolume{
ID: "vol1",
Name: "vol1",
ExternalID: "vol-abcdef",
Namespace: "default",
Topologies: []*CSITopology{{Segments: map[string]string{"AZ1": "123"}}},
AccessMode: CSIVolumeAccessModeSingleNodeWriter,
AttachmentMode: CSIVolumeAttachmentModeBlockDevice,
MountOptions: &CSIMountOptions{FSType: "ext4", MountFlags: []string{"ro", "noatime"}},
Secrets: CSISecrets{"mysecret": "myvalue"},
Parameters: map[string]string{"param1": "val1"},
Context: map[string]string{"ctx1": "val1"},
ReadAllocs: map[string]*Allocation{a1.ID: a1, a2.ID: nil},
WriteAllocs: map[string]*Allocation{a3.ID: a3},
ReadClaims: map[string]*CSIVolumeClaim{a1.ID: c1, a2.ID: c2},
WriteClaims: map[string]*CSIVolumeClaim{a3.ID: c3},
PastClaims: map[string]*CSIVolumeClaim{},
Schedulable: true,
PluginID: "moosefs",
Provider: "n/a",
ProviderVersion: "1.0",
ControllerRequired: true,
ControllersHealthy: 2,
ControllersExpected: 2,
NodesHealthy: 4,
NodesExpected: 5,
ResourceExhausted: time.Now(),
}
v2 := v1.Copy()
if !reflect.DeepEqual(v1, v2) {
t.Fatalf("Copy() returned an unequal Volume; got %#v; want %#v", v1, v2)
}
v1.ReadClaims[a1.ID].State = CSIVolumeClaimStateReadyToFree
v1.ReadAllocs[a2.ID] = a2
v1.WriteAllocs[a3.ID].ClientStatus = AllocClientStatusComplete
v1.MountOptions.FSType = "zfs"
if v2.ReadClaims[a1.ID].State == CSIVolumeClaimStateReadyToFree {
t.Fatalf("Volume.Copy() failed; changes to original ReadClaims seen in copy")
}
if v2.ReadAllocs[a2.ID] != nil {
t.Fatalf("Volume.Copy() failed; changes to original ReadAllocs seen in copy")
}
if v2.WriteAllocs[a3.ID].ClientStatus == AllocClientStatusComplete {
t.Fatalf("Volume.Copy() failed; changes to original WriteAllocs seen in copy")
}
if v2.MountOptions.FSType == "zfs" {
t.Fatalf("Volume.Copy() failed; changes to original MountOptions seen in copy")
}
}
func TestCSIPluginJobs(t *testing.T) {
plug := NewCSIPlugin("foo", 1000)
controller := &Job{
ID: "job",
Type: "service",
TaskGroups: []*TaskGroup{{
Name: "foo",
Count: 11,
Tasks: []*Task{{
CSIPluginConfig: &TaskCSIPluginConfig{
ID: "foo",
Type: CSIPluginTypeController,
},
}},
}},
}
summary := &JobSummary{}
plug.AddJob(controller, summary)
require.Equal(t, 11, plug.ControllersExpected)
// New job id & make it a system node plugin job
node := controller.Copy()
node.ID = "bar"
node.Type = "system"
node.TaskGroups[0].Tasks[0].CSIPluginConfig.Type = CSIPluginTypeNode
summary = &JobSummary{
Summary: map[string]TaskGroupSummary{
"foo": {
Queued: 1,
Running: 1,
Starting: 1,
},
},
}
plug.AddJob(node, summary)
require.Equal(t, 3, plug.NodesExpected)
plug.DeleteJob(node, summary)
require.Equal(t, 0, plug.NodesExpected)
require.Empty(t, plug.NodeJobs[""])
plug.DeleteJob(controller, nil)
require.Equal(t, 0, plug.ControllersExpected)
require.Empty(t, plug.ControllerJobs[""])
}
func TestCSIPluginCleanup(t *testing.T) {
plug := NewCSIPlugin("foo", 1000)
plug.AddPlugin("n0", &CSIInfo{
PluginID: "foo",
AllocID: "a0",
Healthy: true,
Provider: "foo-provider",
RequiresControllerPlugin: true,
RequiresTopologies: false,
ControllerInfo: &CSIControllerInfo{},
})
plug.AddPlugin("n0", &CSIInfo{
PluginID: "foo",
AllocID: "a0",
Healthy: true,
Provider: "foo-provider",
RequiresControllerPlugin: true,
RequiresTopologies: false,
NodeInfo: &CSINodeInfo{},
})
require.Equal(t, 1, plug.ControllersHealthy)
require.Equal(t, 1, plug.NodesHealthy)
err := plug.DeleteNode("n0")
require.NoError(t, err)
require.Equal(t, 0, plug.ControllersHealthy)
require.Equal(t, 0, plug.NodesHealthy)
require.Equal(t, 0, len(plug.Controllers))
require.Equal(t, 0, len(plug.Nodes))
}
func TestDeleteNodeForType_Controller(t *testing.T) {
info := &CSIInfo{
PluginID: "foo",
AllocID: "a0",
Healthy: true,
Provider: "foo-provider",
RequiresControllerPlugin: true,
RequiresTopologies: false,
ControllerInfo: &CSIControllerInfo{},
}
plug := NewCSIPlugin("foo", 1000)
plug.Controllers["n0"] = info
plug.ControllersHealthy = 1
err := plug.DeleteNodeForType("n0", CSIPluginTypeController)
require.NoError(t, err)
require.Equal(t, 0, plug.ControllersHealthy)
require.Equal(t, 0, len(plug.Controllers))
}
func TestDeleteNodeForType_NilController(t *testing.T) {
plug := NewCSIPlugin("foo", 1000)
plug.Controllers["n0"] = nil
plug.ControllersHealthy = 1
err := plug.DeleteNodeForType("n0", CSIPluginTypeController)
require.Error(t, err)
require.Equal(t, 1, len(plug.Controllers))
_, ok := plug.Controllers["foo"]
require.False(t, ok)
}
func TestDeleteNodeForType_Node(t *testing.T) {
info := &CSIInfo{
PluginID: "foo",
AllocID: "a0",
Healthy: true,
Provider: "foo-provider",
RequiresControllerPlugin: true,
RequiresTopologies: false,
NodeInfo: &CSINodeInfo{},
}
plug := NewCSIPlugin("foo", 1000)
plug.Nodes["n0"] = info
plug.NodesHealthy = 1
err := plug.DeleteNodeForType("n0", CSIPluginTypeNode)
require.NoError(t, err)
require.Equal(t, 0, plug.NodesHealthy)
require.Equal(t, 0, len(plug.Nodes))
}
func TestDeleteNodeForType_NilNode(t *testing.T) {
plug := NewCSIPlugin("foo", 1000)
plug.Nodes["n0"] = nil
plug.NodesHealthy = 1
err := plug.DeleteNodeForType("n0", CSIPluginTypeNode)
require.Error(t, err)
require.Equal(t, 1, len(plug.Nodes))
_, ok := plug.Nodes["foo"]
require.False(t, ok)
}
func TestDeleteNodeForType_Monolith(t *testing.T) {
controllerInfo := &CSIInfo{
PluginID: "foo",
AllocID: "a0",
Healthy: true,
Provider: "foo-provider",
RequiresControllerPlugin: true,
RequiresTopologies: false,
ControllerInfo: &CSIControllerInfo{},
}
nodeInfo := &CSIInfo{
PluginID: "foo",
AllocID: "a0",
Healthy: true,
Provider: "foo-provider",
RequiresControllerPlugin: true,
RequiresTopologies: false,
NodeInfo: &CSINodeInfo{},
}
plug := NewCSIPlugin("foo", 1000)
plug.Controllers["n0"] = controllerInfo
plug.ControllersHealthy = 1
plug.Nodes["n0"] = nodeInfo
plug.NodesHealthy = 1
err := plug.DeleteNodeForType("n0", CSIPluginTypeMonolith)
require.NoError(t, err)
require.Equal(t, 0, len(plug.Controllers))
require.Equal(t, 0, len(plug.Nodes))
_, ok := plug.Nodes["foo"]
require.False(t, ok)
_, ok = plug.Controllers["foo"]
require.False(t, ok)
}
func TestDeleteNodeForType_Monolith_NilController(t *testing.T) {
plug := NewCSIPlugin("foo", 1000)
plug.Controllers["n0"] = nil
plug.ControllersHealthy = 1
nodeInfo := &CSIInfo{
PluginID: "foo",
AllocID: "a0",
Healthy: true,
Provider: "foo-provider",
RequiresControllerPlugin: true,
RequiresTopologies: false,
NodeInfo: &CSINodeInfo{},
}
plug.Nodes["n0"] = nodeInfo
plug.NodesHealthy = 1
err := plug.DeleteNodeForType("n0", CSIPluginTypeMonolith)
require.Error(t, err)
require.Equal(t, 1, len(plug.Controllers))
require.Equal(t, 0, len(plug.Nodes))
_, ok := plug.Nodes["foo"]
require.False(t, ok)
_, ok = plug.Controllers["foo"]
require.False(t, ok)
}
func TestDeleteNodeForType_Monolith_NilNode(t *testing.T) {
plug := NewCSIPlugin("foo", 1000)
plug.Nodes["n0"] = nil
plug.NodesHealthy = 1
controllerInfo := &CSIInfo{
PluginID: "foo",
AllocID: "a0",
Healthy: true,
Provider: "foo-provider",
RequiresControllerPlugin: true,
RequiresTopologies: false,
ControllerInfo: &CSIControllerInfo{},
}
plug.Controllers["n0"] = controllerInfo
plug.ControllersHealthy = 1
err := plug.DeleteNodeForType("n0", CSIPluginTypeMonolith)
require.Error(t, err)
require.Equal(t, 0, len(plug.Controllers))
require.Equal(t, 1, len(plug.Nodes))
_, ok := plug.Nodes["foo"]
require.False(t, ok)
_, ok = plug.Controllers["foo"]
require.False(t, ok)
}