1a10433b97
This changeset is some pre-requisite boilerplate that is required for introducing CSI volume management for client nodes. It extracts out fingerprinting logic from the csi instance manager. This change is to facilitate reusing the csimanager to also manage the node-local CSI functionality, as it is the easiest place for us to guaruntee health checking and to provide additional visibility into the running operations through the fingerprinter mechanism and goroutine. It also introduces the VolumeMounter interface that will be used to manage staging/publishing unstaging/unpublishing of volumes on the host.
278 lines
6.9 KiB
Go
278 lines
6.9 KiB
Go
package csimanager
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
"github.com/hashicorp/nomad/plugins/csi"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestBuildBasicFingerprint_Node(t *testing.T) {
|
|
tt := []struct {
|
|
Name string
|
|
|
|
Capabilities *csi.PluginCapabilitySet
|
|
CapabilitiesErr error
|
|
CapabilitiesCallCount int64
|
|
|
|
NodeInfo *csi.NodeGetInfoResponse
|
|
NodeInfoErr error
|
|
NodeInfoCallCount int64
|
|
|
|
ExpectedCSIInfo *structs.CSIInfo
|
|
ExpectedErr error
|
|
}{
|
|
{
|
|
Name: "Minimal successful response",
|
|
|
|
Capabilities: &csi.PluginCapabilitySet{},
|
|
CapabilitiesCallCount: 1,
|
|
|
|
NodeInfo: &csi.NodeGetInfoResponse{
|
|
NodeID: "foobar",
|
|
MaxVolumes: 5,
|
|
AccessibleTopology: nil,
|
|
},
|
|
NodeInfoCallCount: 1,
|
|
|
|
ExpectedCSIInfo: &structs.CSIInfo{
|
|
PluginID: "test-plugin",
|
|
Healthy: false,
|
|
HealthDescription: "initial fingerprint not completed",
|
|
NodeInfo: &structs.CSINodeInfo{
|
|
ID: "foobar",
|
|
MaxVolumes: 5,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "Successful response with capabilities and topologies",
|
|
|
|
Capabilities: csi.NewTestPluginCapabilitySet(true, false),
|
|
CapabilitiesCallCount: 1,
|
|
|
|
NodeInfo: &csi.NodeGetInfoResponse{
|
|
NodeID: "foobar",
|
|
MaxVolumes: 5,
|
|
AccessibleTopology: &csi.Topology{
|
|
Segments: map[string]string{
|
|
"com.hashicorp.nomad/node-id": "foobar",
|
|
},
|
|
},
|
|
},
|
|
NodeInfoCallCount: 1,
|
|
|
|
ExpectedCSIInfo: &structs.CSIInfo{
|
|
PluginID: "test-plugin",
|
|
Healthy: false,
|
|
HealthDescription: "initial fingerprint not completed",
|
|
|
|
RequiresTopologies: true,
|
|
|
|
NodeInfo: &structs.CSINodeInfo{
|
|
ID: "foobar",
|
|
MaxVolumes: 5,
|
|
AccessibleTopology: &structs.CSITopology{
|
|
Segments: map[string]string{
|
|
"com.hashicorp.nomad/node-id": "foobar",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "PluginGetCapabilities Failed",
|
|
|
|
CapabilitiesErr: errors.New("request failed"),
|
|
CapabilitiesCallCount: 1,
|
|
|
|
NodeInfoCallCount: 0,
|
|
|
|
ExpectedCSIInfo: &structs.CSIInfo{
|
|
PluginID: "test-plugin",
|
|
Healthy: false,
|
|
HealthDescription: "initial fingerprint not completed",
|
|
NodeInfo: &structs.CSINodeInfo{},
|
|
},
|
|
ExpectedErr: errors.New("request failed"),
|
|
},
|
|
{
|
|
Name: "NodeGetInfo Failed",
|
|
|
|
Capabilities: &csi.PluginCapabilitySet{},
|
|
CapabilitiesCallCount: 1,
|
|
|
|
NodeInfoErr: errors.New("request failed"),
|
|
NodeInfoCallCount: 1,
|
|
|
|
ExpectedCSIInfo: &structs.CSIInfo{
|
|
PluginID: "test-plugin",
|
|
Healthy: false,
|
|
HealthDescription: "initial fingerprint not completed",
|
|
NodeInfo: &structs.CSINodeInfo{},
|
|
},
|
|
ExpectedErr: errors.New("request failed"),
|
|
},
|
|
}
|
|
|
|
for _, test := range tt {
|
|
t.Run(test.Name, func(t *testing.T) {
|
|
client, im := setupTestNodeInstanceManager(t)
|
|
|
|
client.NextPluginGetCapabilitiesResponse = test.Capabilities
|
|
client.NextPluginGetCapabilitiesErr = test.CapabilitiesErr
|
|
|
|
client.NextNodeGetInfoResponse = test.NodeInfo
|
|
client.NextNodeGetInfoErr = test.NodeInfoErr
|
|
|
|
info, err := im.fp.buildBasicFingerprint(context.TODO())
|
|
|
|
require.Equal(t, test.ExpectedCSIInfo, info)
|
|
require.Equal(t, test.ExpectedErr, err)
|
|
|
|
require.Equal(t, test.CapabilitiesCallCount, client.PluginGetCapabilitiesCallCount)
|
|
require.Equal(t, test.NodeInfoCallCount, client.NodeGetInfoCallCount)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBuildControllerFingerprint(t *testing.T) {
|
|
tt := []struct {
|
|
Name string
|
|
|
|
Capabilities *csi.ControllerCapabilitySet
|
|
CapabilitiesErr error
|
|
CapabilitiesCallCount int64
|
|
|
|
ProbeResponse bool
|
|
ProbeErr error
|
|
ProbeCallCount int64
|
|
|
|
ExpectedControllerInfo *structs.CSIControllerInfo
|
|
ExpectedErr error
|
|
}{
|
|
{
|
|
Name: "Minimal successful response",
|
|
|
|
Capabilities: &csi.ControllerCapabilitySet{},
|
|
CapabilitiesCallCount: 1,
|
|
|
|
ProbeResponse: true,
|
|
ProbeCallCount: 1,
|
|
|
|
ExpectedControllerInfo: &structs.CSIControllerInfo{},
|
|
},
|
|
{
|
|
Name: "Successful response with capabilities",
|
|
|
|
Capabilities: &csi.ControllerCapabilitySet{
|
|
HasListVolumes: true,
|
|
},
|
|
CapabilitiesCallCount: 1,
|
|
|
|
ProbeResponse: true,
|
|
ProbeCallCount: 1,
|
|
|
|
ExpectedControllerInfo: &structs.CSIControllerInfo{
|
|
SupportsListVolumes: true,
|
|
},
|
|
},
|
|
{
|
|
Name: "ControllerGetCapabilities Failed",
|
|
|
|
CapabilitiesErr: errors.New("request failed"),
|
|
CapabilitiesCallCount: 1,
|
|
|
|
ProbeResponse: true,
|
|
ProbeCallCount: 1,
|
|
|
|
ExpectedControllerInfo: &structs.CSIControllerInfo{},
|
|
ExpectedErr: errors.New("request failed"),
|
|
},
|
|
}
|
|
|
|
for _, test := range tt {
|
|
t.Run(test.Name, func(t *testing.T) {
|
|
client, im := setupTestNodeInstanceManager(t)
|
|
|
|
client.NextControllerGetCapabilitiesResponse = test.Capabilities
|
|
client.NextControllerGetCapabilitiesErr = test.CapabilitiesErr
|
|
|
|
client.NextPluginProbeResponse = test.ProbeResponse
|
|
client.NextPluginProbeErr = test.ProbeErr
|
|
|
|
info, err := im.fp.buildControllerFingerprint(context.TODO(), &structs.CSIInfo{ControllerInfo: &structs.CSIControllerInfo{}})
|
|
|
|
require.Equal(t, test.ExpectedControllerInfo, info.ControllerInfo)
|
|
require.Equal(t, test.ExpectedErr, err)
|
|
|
|
require.Equal(t, test.CapabilitiesCallCount, client.ControllerGetCapabilitiesCallCount)
|
|
require.Equal(t, test.ProbeCallCount, client.PluginProbeCallCount)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBuildNodeFingerprint(t *testing.T) {
|
|
tt := []struct {
|
|
Name string
|
|
|
|
Capabilities *csi.NodeCapabilitySet
|
|
CapabilitiesErr error
|
|
CapabilitiesCallCount int64
|
|
|
|
ExpectedCSINodeInfo *structs.CSINodeInfo
|
|
ExpectedErr error
|
|
}{
|
|
{
|
|
Name: "Minimal successful response",
|
|
|
|
Capabilities: &csi.NodeCapabilitySet{},
|
|
CapabilitiesCallCount: 1,
|
|
|
|
ExpectedCSINodeInfo: &structs.CSINodeInfo{
|
|
RequiresNodeStageVolume: false,
|
|
},
|
|
},
|
|
{
|
|
Name: "Successful response with capabilities and topologies",
|
|
|
|
Capabilities: &csi.NodeCapabilitySet{
|
|
HasStageUnstageVolume: true,
|
|
},
|
|
CapabilitiesCallCount: 1,
|
|
|
|
ExpectedCSINodeInfo: &structs.CSINodeInfo{
|
|
RequiresNodeStageVolume: true,
|
|
},
|
|
},
|
|
{
|
|
Name: "NodeGetCapabilities Failed",
|
|
|
|
CapabilitiesErr: errors.New("request failed"),
|
|
CapabilitiesCallCount: 1,
|
|
|
|
ExpectedCSINodeInfo: &structs.CSINodeInfo{},
|
|
ExpectedErr: errors.New("request failed"),
|
|
},
|
|
}
|
|
|
|
for _, test := range tt {
|
|
t.Run(test.Name, func(t *testing.T) {
|
|
client, im := setupTestNodeInstanceManager(t)
|
|
|
|
client.NextNodeGetCapabilitiesResponse = test.Capabilities
|
|
client.NextNodeGetCapabilitiesErr = test.CapabilitiesErr
|
|
|
|
info, err := im.fp.buildNodeFingerprint(context.TODO(), &structs.CSIInfo{NodeInfo: &structs.CSINodeInfo{}})
|
|
|
|
require.Equal(t, test.ExpectedCSINodeInfo, info.NodeInfo)
|
|
require.Equal(t, test.ExpectedErr, err)
|
|
|
|
require.Equal(t, test.CapabilitiesCallCount, client.NodeGetCapabilitiesCallCount)
|
|
})
|
|
}
|
|
}
|