You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
454 lines
13 KiB
454 lines
13 KiB
package csi
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/container-storage-interface/spec/lib/go/csi"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
)
|
|
|
|
// testVolCaps returns a standard volume capability for testing.
|
|
func testVolCaps() []*csi.VolumeCapability {
|
|
return []*csi.VolumeCapability{{
|
|
AccessType: &csi.VolumeCapability_Mount{
|
|
Mount: &csi.VolumeCapability_MountVolume{FsType: "ext4"},
|
|
},
|
|
AccessMode: &csi.VolumeCapability_AccessMode{
|
|
Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
|
|
},
|
|
}}
|
|
}
|
|
|
|
func testVolCap() *csi.VolumeCapability {
|
|
return testVolCaps()[0]
|
|
}
|
|
|
|
func TestController_CreateVolume(t *testing.T) {
|
|
mgr := newTestManager(t)
|
|
backend := NewLocalVolumeBackend(mgr)
|
|
cs := &controllerServer{backend: backend}
|
|
|
|
resp, err := cs.CreateVolume(context.Background(), &csi.CreateVolumeRequest{
|
|
Name: "test-vol",
|
|
VolumeCapabilities: testVolCaps(),
|
|
CapacityRange: &csi.CapacityRange{
|
|
RequiredBytes: 4 * 1024 * 1024,
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("CreateVolume: %v", err)
|
|
}
|
|
if resp.Volume.VolumeId != "test-vol" {
|
|
t.Fatalf("volume_id: got %q, want %q", resp.Volume.VolumeId, "test-vol")
|
|
}
|
|
if resp.Volume.CapacityBytes != 4*1024*1024 {
|
|
t.Fatalf("capacity: got %d, want %d", resp.Volume.CapacityBytes, 4*1024*1024)
|
|
}
|
|
if !mgr.VolumeExists("test-vol") {
|
|
t.Fatal("expected volume to exist")
|
|
}
|
|
|
|
// Verify volume_context has iSCSI info.
|
|
if resp.Volume.VolumeContext == nil {
|
|
t.Fatal("expected volume_context to be set")
|
|
}
|
|
if resp.Volume.VolumeContext["iqn"] == "" {
|
|
t.Fatal("expected iqn in volume_context")
|
|
}
|
|
}
|
|
|
|
func TestController_CreateIdempotent(t *testing.T) {
|
|
mgr := newTestManager(t)
|
|
backend := NewLocalVolumeBackend(mgr)
|
|
cs := &controllerServer{backend: backend}
|
|
|
|
req := &csi.CreateVolumeRequest{
|
|
Name: "idem-vol",
|
|
VolumeCapabilities: testVolCaps(),
|
|
CapacityRange: &csi.CapacityRange{
|
|
RequiredBytes: 4 * 1024 * 1024,
|
|
},
|
|
}
|
|
|
|
if _, err := cs.CreateVolume(context.Background(), req); err != nil {
|
|
t.Fatalf("first create: %v", err)
|
|
}
|
|
|
|
// Second create with same size should succeed (idempotent).
|
|
resp, err := cs.CreateVolume(context.Background(), req)
|
|
if err != nil {
|
|
t.Fatalf("second create: %v", err)
|
|
}
|
|
if resp.Volume.VolumeId != "idem-vol" {
|
|
t.Fatalf("volume_id: got %q, want %q", resp.Volume.VolumeId, "idem-vol")
|
|
}
|
|
}
|
|
|
|
func TestController_DeleteVolume(t *testing.T) {
|
|
mgr := newTestManager(t)
|
|
backend := NewLocalVolumeBackend(mgr)
|
|
cs := &controllerServer{backend: backend}
|
|
|
|
// Create then delete.
|
|
_, err := cs.CreateVolume(context.Background(), &csi.CreateVolumeRequest{
|
|
Name: "del-vol",
|
|
VolumeCapabilities: testVolCaps(),
|
|
CapacityRange: &csi.CapacityRange{
|
|
RequiredBytes: 4 * 1024 * 1024,
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create: %v", err)
|
|
}
|
|
|
|
_, err = cs.DeleteVolume(context.Background(), &csi.DeleteVolumeRequest{
|
|
VolumeId: "del-vol",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("delete: %v", err)
|
|
}
|
|
if mgr.VolumeExists("del-vol") {
|
|
t.Fatal("expected volume to not exist after delete")
|
|
}
|
|
}
|
|
|
|
func TestController_DeleteNotFound(t *testing.T) {
|
|
mgr := newTestManager(t)
|
|
backend := NewLocalVolumeBackend(mgr)
|
|
cs := &controllerServer{backend: backend}
|
|
|
|
// Delete non-existent volume -- should succeed (CSI spec idempotency).
|
|
_, err := cs.DeleteVolume(context.Background(), &csi.DeleteVolumeRequest{
|
|
VolumeId: "nonexistent",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("delete non-existent: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestControllerPublish_HappyPath(t *testing.T) {
|
|
mgr := newTestManager(t)
|
|
backend := NewLocalVolumeBackend(mgr)
|
|
cs := &controllerServer{backend: backend}
|
|
|
|
// Create a volume first.
|
|
mgr.CreateVolume("pub-vol", 4*1024*1024)
|
|
|
|
resp, err := cs.ControllerPublishVolume(context.Background(), &csi.ControllerPublishVolumeRequest{
|
|
VolumeId: "pub-vol",
|
|
NodeId: "node-1",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("ControllerPublishVolume: %v", err)
|
|
}
|
|
if resp.PublishContext == nil {
|
|
t.Fatal("expected publish_context")
|
|
}
|
|
if resp.PublishContext["iscsiAddr"] == "" {
|
|
t.Fatal("expected iscsiAddr in publish_context")
|
|
}
|
|
if resp.PublishContext["iqn"] == "" {
|
|
t.Fatal("expected iqn in publish_context")
|
|
}
|
|
}
|
|
|
|
func TestControllerPublish_MissingVolumeID(t *testing.T) {
|
|
mgr := newTestManager(t)
|
|
backend := NewLocalVolumeBackend(mgr)
|
|
cs := &controllerServer{backend: backend}
|
|
|
|
_, err := cs.ControllerPublishVolume(context.Background(), &csi.ControllerPublishVolumeRequest{
|
|
NodeId: "node-1",
|
|
})
|
|
if err == nil {
|
|
t.Fatal("expected error for missing volume ID")
|
|
}
|
|
st, _ := status.FromError(err)
|
|
if st.Code() != codes.InvalidArgument {
|
|
t.Fatalf("expected InvalidArgument, got %v", st.Code())
|
|
}
|
|
}
|
|
|
|
func TestControllerPublish_MissingNodeID(t *testing.T) {
|
|
mgr := newTestManager(t)
|
|
backend := NewLocalVolumeBackend(mgr)
|
|
cs := &controllerServer{backend: backend}
|
|
|
|
_, err := cs.ControllerPublishVolume(context.Background(), &csi.ControllerPublishVolumeRequest{
|
|
VolumeId: "vol1",
|
|
})
|
|
if err == nil {
|
|
t.Fatal("expected error for missing node ID")
|
|
}
|
|
st, _ := status.FromError(err)
|
|
if st.Code() != codes.InvalidArgument {
|
|
t.Fatalf("expected InvalidArgument, got %v", st.Code())
|
|
}
|
|
}
|
|
|
|
func TestControllerPublish_NotFound(t *testing.T) {
|
|
mgr := newTestManager(t)
|
|
backend := NewLocalVolumeBackend(mgr)
|
|
cs := &controllerServer{backend: backend}
|
|
|
|
_, err := cs.ControllerPublishVolume(context.Background(), &csi.ControllerPublishVolumeRequest{
|
|
VolumeId: "nonexistent",
|
|
NodeId: "node-1",
|
|
})
|
|
if err == nil {
|
|
t.Fatal("expected error for not found")
|
|
}
|
|
st, _ := status.FromError(err)
|
|
if st.Code() != codes.NotFound {
|
|
t.Fatalf("expected NotFound, got %v", st.Code())
|
|
}
|
|
}
|
|
|
|
func TestControllerUnpublish_Success(t *testing.T) {
|
|
mgr := newTestManager(t)
|
|
backend := NewLocalVolumeBackend(mgr)
|
|
cs := &controllerServer{backend: backend}
|
|
|
|
_, err := cs.ControllerUnpublishVolume(context.Background(), &csi.ControllerUnpublishVolumeRequest{
|
|
VolumeId: "any-vol",
|
|
NodeId: "node-1",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("ControllerUnpublishVolume: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestController_Capabilities(t *testing.T) {
|
|
mgr := newTestManager(t)
|
|
backend := NewLocalVolumeBackend(mgr)
|
|
cs := &controllerServer{backend: backend}
|
|
|
|
resp, err := cs.ControllerGetCapabilities(context.Background(), &csi.ControllerGetCapabilitiesRequest{})
|
|
if err != nil {
|
|
t.Fatalf("ControllerGetCapabilities: %v", err)
|
|
}
|
|
|
|
want := map[csi.ControllerServiceCapability_RPC_Type]bool{
|
|
csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME: true,
|
|
csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME: true,
|
|
csi.ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT: true,
|
|
csi.ControllerServiceCapability_RPC_LIST_SNAPSHOTS: true,
|
|
csi.ControllerServiceCapability_RPC_EXPAND_VOLUME: true,
|
|
}
|
|
|
|
got := make(map[csi.ControllerServiceCapability_RPC_Type]bool)
|
|
for _, cap := range resp.Capabilities {
|
|
rpc := cap.GetRpc()
|
|
if rpc != nil {
|
|
got[rpc.Type] = true
|
|
}
|
|
}
|
|
|
|
for capType := range want {
|
|
if !got[capType] {
|
|
t.Fatalf("missing capability: %v", capType)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestController_CreateSnapshot(t *testing.T) {
|
|
mgr := newTestManager(t)
|
|
backend := NewLocalVolumeBackend(mgr)
|
|
cs := &controllerServer{backend: backend}
|
|
|
|
// Create volume first.
|
|
cs.CreateVolume(context.Background(), &csi.CreateVolumeRequest{
|
|
Name: "snap-vol",
|
|
VolumeCapabilities: testVolCaps(),
|
|
CapacityRange: &csi.CapacityRange{RequiredBytes: 4 * 1024 * 1024},
|
|
})
|
|
|
|
resp, err := cs.CreateSnapshot(context.Background(), &csi.CreateSnapshotRequest{
|
|
Name: "my-snapshot",
|
|
SourceVolumeId: "snap-vol",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("CreateSnapshot: %v", err)
|
|
}
|
|
if resp.Snapshot.SnapshotId == "" {
|
|
t.Fatal("snapshot ID should not be empty")
|
|
}
|
|
if resp.Snapshot.SourceVolumeId != "snap-vol" {
|
|
t.Fatalf("SourceVolumeId: got %q, want snap-vol", resp.Snapshot.SourceVolumeId)
|
|
}
|
|
if !resp.Snapshot.ReadyToUse {
|
|
t.Fatal("snapshot should be ready")
|
|
}
|
|
}
|
|
|
|
func TestController_CreateSnapshotIdempotent(t *testing.T) {
|
|
mgr := newTestManager(t)
|
|
backend := NewLocalVolumeBackend(mgr)
|
|
cs := &controllerServer{backend: backend}
|
|
|
|
cs.CreateVolume(context.Background(), &csi.CreateVolumeRequest{
|
|
Name: "snap-vol",
|
|
VolumeCapabilities: testVolCaps(),
|
|
CapacityRange: &csi.CapacityRange{RequiredBytes: 4 * 1024 * 1024},
|
|
})
|
|
|
|
// First create.
|
|
resp1, err := cs.CreateSnapshot(context.Background(), &csi.CreateSnapshotRequest{
|
|
Name: "my-snapshot",
|
|
SourceVolumeId: "snap-vol",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("first CreateSnapshot: %v", err)
|
|
}
|
|
|
|
// Same name on same volume -- CSI idempotency: should return existing snapshot.
|
|
resp2, err := cs.CreateSnapshot(context.Background(), &csi.CreateSnapshotRequest{
|
|
Name: "my-snapshot",
|
|
SourceVolumeId: "snap-vol",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("idempotent CreateSnapshot: %v", err)
|
|
}
|
|
if resp2.Snapshot.SnapshotId != resp1.Snapshot.SnapshotId {
|
|
t.Fatalf("snapshot IDs differ: %q vs %q", resp2.Snapshot.SnapshotId, resp1.Snapshot.SnapshotId)
|
|
}
|
|
if resp2.Snapshot.SourceVolumeId != "snap-vol" {
|
|
t.Fatalf("source volume: got %q", resp2.Snapshot.SourceVolumeId)
|
|
}
|
|
}
|
|
|
|
func TestController_DeleteSnapshot(t *testing.T) {
|
|
mgr := newTestManager(t)
|
|
backend := NewLocalVolumeBackend(mgr)
|
|
cs := &controllerServer{backend: backend}
|
|
|
|
cs.CreateVolume(context.Background(), &csi.CreateVolumeRequest{
|
|
Name: "snap-vol",
|
|
VolumeCapabilities: testVolCaps(),
|
|
CapacityRange: &csi.CapacityRange{RequiredBytes: 4 * 1024 * 1024},
|
|
})
|
|
|
|
resp, _ := cs.CreateSnapshot(context.Background(), &csi.CreateSnapshotRequest{
|
|
Name: "del-snap",
|
|
SourceVolumeId: "snap-vol",
|
|
})
|
|
|
|
_, err := cs.DeleteSnapshot(context.Background(), &csi.DeleteSnapshotRequest{
|
|
SnapshotId: resp.Snapshot.SnapshotId,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("DeleteSnapshot: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestController_DeleteSnapshotNotFound(t *testing.T) {
|
|
mgr := newTestManager(t)
|
|
backend := NewLocalVolumeBackend(mgr)
|
|
cs := &controllerServer{backend: backend}
|
|
|
|
// Delete non-existent snapshot should succeed (idempotent).
|
|
_, err := cs.DeleteSnapshot(context.Background(), &csi.DeleteSnapshotRequest{
|
|
SnapshotId: "snap-nonexistent-vol-42",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("DeleteSnapshot non-existent: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestController_ListSnapshotsByVolume(t *testing.T) {
|
|
mgr := newTestManager(t)
|
|
backend := NewLocalVolumeBackend(mgr)
|
|
cs := &controllerServer{backend: backend}
|
|
|
|
cs.CreateVolume(context.Background(), &csi.CreateVolumeRequest{
|
|
Name: "list-vol",
|
|
VolumeCapabilities: testVolCaps(),
|
|
CapacityRange: &csi.CapacityRange{RequiredBytes: 4 * 1024 * 1024},
|
|
})
|
|
|
|
cs.CreateSnapshot(context.Background(), &csi.CreateSnapshotRequest{
|
|
Name: "snap-a", SourceVolumeId: "list-vol",
|
|
})
|
|
cs.CreateSnapshot(context.Background(), &csi.CreateSnapshotRequest{
|
|
Name: "snap-b", SourceVolumeId: "list-vol",
|
|
})
|
|
|
|
resp, err := cs.ListSnapshots(context.Background(), &csi.ListSnapshotsRequest{
|
|
SourceVolumeId: "list-vol",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("ListSnapshots: %v", err)
|
|
}
|
|
if len(resp.Entries) != 2 {
|
|
t.Fatalf("expected 2 snapshots, got %d", len(resp.Entries))
|
|
}
|
|
}
|
|
|
|
func TestController_ExpandVolume(t *testing.T) {
|
|
mgr := newTestManager(t)
|
|
backend := NewLocalVolumeBackend(mgr)
|
|
cs := &controllerServer{backend: backend}
|
|
|
|
cs.CreateVolume(context.Background(), &csi.CreateVolumeRequest{
|
|
Name: "expand-vol",
|
|
VolumeCapabilities: testVolCaps(),
|
|
CapacityRange: &csi.CapacityRange{RequiredBytes: 4 * 1024 * 1024},
|
|
})
|
|
|
|
resp, err := cs.ControllerExpandVolume(context.Background(), &csi.ControllerExpandVolumeRequest{
|
|
VolumeId: "expand-vol",
|
|
CapacityRange: &csi.CapacityRange{RequiredBytes: 8 * 1024 * 1024},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("ControllerExpandVolume: %v", err)
|
|
}
|
|
if resp.CapacityBytes != 8*1024*1024 {
|
|
t.Fatalf("CapacityBytes: got %d, want %d", resp.CapacityBytes, 8*1024*1024)
|
|
}
|
|
if !resp.NodeExpansionRequired {
|
|
t.Fatal("NodeExpansionRequired should be true")
|
|
}
|
|
}
|
|
|
|
func TestController_ExpandNoShrink(t *testing.T) {
|
|
mgr := newTestManager(t)
|
|
backend := NewLocalVolumeBackend(mgr)
|
|
cs := &controllerServer{backend: backend}
|
|
|
|
cs.CreateVolume(context.Background(), &csi.CreateVolumeRequest{
|
|
Name: "noshrink-vol",
|
|
VolumeCapabilities: testVolCaps(),
|
|
CapacityRange: &csi.CapacityRange{RequiredBytes: 8 * 1024 * 1024},
|
|
})
|
|
|
|
_, err := cs.ControllerExpandVolume(context.Background(), &csi.ControllerExpandVolumeRequest{
|
|
VolumeId: "noshrink-vol",
|
|
CapacityRange: &csi.CapacityRange{RequiredBytes: 4 * 1024 * 1024},
|
|
})
|
|
if err == nil {
|
|
t.Fatal("expected error for shrink")
|
|
}
|
|
st, _ := status.FromError(err)
|
|
if st.Code() != codes.InvalidArgument {
|
|
t.Fatalf("expected InvalidArgument, got %v", st.Code())
|
|
}
|
|
}
|
|
|
|
func TestController_ExpandMissingVolumeID(t *testing.T) {
|
|
mgr := newTestManager(t)
|
|
backend := NewLocalVolumeBackend(mgr)
|
|
cs := &controllerServer{backend: backend}
|
|
|
|
_, err := cs.ControllerExpandVolume(context.Background(), &csi.ControllerExpandVolumeRequest{
|
|
CapacityRange: &csi.CapacityRange{RequiredBytes: 8 * 1024 * 1024},
|
|
})
|
|
if err == nil {
|
|
t.Fatal("expected error for missing volume ID")
|
|
}
|
|
st, _ := status.FromError(err)
|
|
if st.Code() != codes.InvalidArgument {
|
|
t.Fatalf("expected InvalidArgument, got %v", st.Code())
|
|
}
|
|
}
|