-
Notifications
You must be signed in to change notification settings - Fork 59
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Advertise ListVolumes which allows usage of Volume Health Monitoring * Set abnormal on EIO If we get an EIO error from statsfs then its safe to assume the PV is not happy * Add unit tests for ListVolumes The tests validate most of the returned fields of the Volume part of the response but not the VolumeContext and ContentSource fields. All of the fields of the VolumeStatus are validated. The fake LinodeClient has been added in order to easily mock Linode API responses.
- Loading branch information
Showing
6 changed files
with
323 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package linodebs | ||
|
||
import "github.com/container-storage-interface/spec/lib/go/csi" | ||
|
||
func controllerCapabilities() []csi.ControllerServiceCapability_RPC_Type { | ||
return []csi.ControllerServiceCapability_RPC_Type{ | ||
csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME, | ||
csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME, | ||
// csi.ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT, | ||
// csi.ControllerServiceCapability_RPC_LIST_SNAPSHOTS, | ||
csi.ControllerServiceCapability_RPC_PUBLISH_READONLY, | ||
csi.ControllerServiceCapability_RPC_EXPAND_VOLUME, | ||
csi.ControllerServiceCapability_RPC_CLONE_VOLUME, | ||
csi.ControllerServiceCapability_RPC_LIST_VOLUMES, | ||
csi.ControllerServiceCapability_RPC_VOLUME_CONDITION, | ||
} | ||
} | ||
|
||
func nodeCapabilities() []csi.NodeServiceCapability_RPC_Type { | ||
return []csi.NodeServiceCapability_RPC_Type{ | ||
csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME, | ||
csi.NodeServiceCapability_RPC_EXPAND_VOLUME, | ||
csi.NodeServiceCapability_RPC_GET_VOLUME_STATS, | ||
csi.NodeServiceCapability_RPC_VOLUME_CONDITION, | ||
} | ||
} | ||
|
||
func volumeCapabilitiesAccessMode() []csi.VolumeCapability_AccessMode_Mode { | ||
return []csi.VolumeCapability_AccessMode_Mode{ | ||
csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, | ||
// csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,231 @@ | ||
package linodebs | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/container-storage-interface/spec/lib/go/csi" | ||
"github.com/linode/linode-blockstorage-csi-driver/pkg/common" | ||
"github.com/linode/linodego" | ||
) | ||
|
||
func TestListVolumes(t *testing.T) { | ||
cases := map[string]struct { | ||
volumes []linodego.Volume | ||
throwErr bool | ||
}{ | ||
"volume attached to node": { | ||
volumes: []linodego.Volume{ | ||
{ | ||
ID: 1, | ||
Label: "foo", | ||
Status: "", | ||
Region: "danmaaag", | ||
Size: 30, | ||
LinodeID: createLinodeID(10), | ||
FilesystemPath: "", | ||
Tags: []string{}, | ||
}, | ||
}, | ||
throwErr: false, | ||
}, | ||
"volume not attached": { | ||
volumes: []linodego.Volume{ | ||
{ | ||
ID: 1, | ||
Label: "bar", | ||
Status: "", | ||
Region: "", | ||
Size: 30, | ||
FilesystemPath: "", | ||
}, | ||
}, | ||
throwErr: false, | ||
}, | ||
"multiple volumes - with attachments": { | ||
volumes: []linodego.Volume{ | ||
{ | ||
ID: 1, | ||
Label: "foo", | ||
Status: "", | ||
Region: "", | ||
Size: 30, | ||
LinodeID: createLinodeID(5), | ||
FilesystemPath: "", | ||
Tags: []string{}, | ||
}, | ||
{ | ||
ID: 2, | ||
Label: "foo", | ||
Status: "", | ||
Region: "", | ||
Size: 60, | ||
FilesystemPath: "", | ||
Tags: []string{}, | ||
LinodeID: createLinodeID(10), | ||
}, | ||
}, | ||
throwErr: false, | ||
}, | ||
"multiple volumes - mixed attachments": { | ||
volumes: []linodego.Volume{ | ||
{ | ||
ID: 1, | ||
Label: "foo", | ||
Status: "", | ||
Region: "", | ||
Size: 30, | ||
LinodeID: createLinodeID(5), | ||
FilesystemPath: "", | ||
Tags: []string{}, | ||
}, | ||
{ | ||
ID: 2, | ||
Label: "foo", | ||
Status: "", | ||
Region: "", | ||
Size: 30, | ||
FilesystemPath: "", | ||
Tags: []string{}, | ||
LinodeID: nil, | ||
}, | ||
}, | ||
throwErr: false, | ||
}, | ||
"Linode API error": { | ||
volumes: nil, | ||
throwErr: true, | ||
}, | ||
} | ||
|
||
for c, tt := range cases { | ||
t.Run(c, func(t *testing.T) { | ||
cs := &LinodeControllerServer{ | ||
CloudProvider: &fakeLinodeClient{ | ||
volumes: tt.volumes, | ||
throwErr: tt.throwErr, | ||
}, | ||
} | ||
|
||
listVolsResp, err := cs.ListVolumes(context.Background(), &csi.ListVolumesRequest{}) | ||
if err != nil { | ||
if !tt.throwErr { | ||
t.Fatalf("test case got unexpected err: %s", err) | ||
} | ||
return | ||
} | ||
|
||
for _, entry := range listVolsResp.Entries { | ||
gotVol := entry.GetVolume() | ||
if gotVol == nil { | ||
t.Fatal("vol was nil") | ||
} | ||
|
||
var wantVol *linodego.Volume | ||
for _, v := range tt.volumes { | ||
v := v | ||
// The issue is that the ID returned is | ||
// not the same as what is passed in | ||
key := common.CreateLinodeVolumeKey(v.ID, v.Label) | ||
if gotVol.VolumeId == key.GetVolumeKey() { | ||
wantVol = &v | ||
break | ||
} | ||
} | ||
|
||
if wantVol == nil { | ||
t.Fatalf("failed to find input volume equivalent to: %#v", gotVol) | ||
} | ||
|
||
if gotVol.CapacityBytes != int64(wantVol.Size)*gigabyte { | ||
t.Errorf("volume size not equal, got: %d, want: %d", gotVol.CapacityBytes, wantVol.Size*gigabyte) | ||
} | ||
|
||
for _, i := range gotVol.GetAccessibleTopology() { | ||
region, ok := i.Segments[VolumeTopologyRegion] | ||
if !ok { | ||
t.Errorf("got empty region") | ||
} | ||
|
||
if region != wantVol.Region { | ||
t.Errorf("regions do not match, got: %s, want: %s", region, wantVol.Region) | ||
} | ||
} | ||
|
||
status := entry.GetStatus() | ||
if status == nil { | ||
t.Fatal("status was nil") | ||
} | ||
|
||
if status.VolumeCondition.Abnormal { | ||
t.Errorf("got abnormal volume condition") | ||
} | ||
|
||
if len(status.GetPublishedNodeIds()) > 1 { | ||
t.Errorf("volume was published on more than 1 node, got: %s", status.GetPublishedNodeIds()) | ||
} | ||
|
||
switch publishedNodes := status.GetPublishedNodeIds(); { | ||
case len(publishedNodes) == 0 && wantVol.LinodeID == nil: | ||
// This case is fine - having it here prevents a segfault if we try to index into publishedNodes in the last case | ||
case len(publishedNodes) == 0 && wantVol.LinodeID != nil: | ||
t.Errorf("expected volume to be attached, got: %s, want: %d", status.GetPublishedNodeIds(), *wantVol.LinodeID) | ||
case len(publishedNodes) != 0 && wantVol.LinodeID == nil: | ||
t.Errorf("expected volume to be unattached, got: %s", publishedNodes) | ||
case publishedNodes[0] != fmt.Sprintf("%d", *wantVol.LinodeID): | ||
t.Fatalf("got: %s, want: %d published node id", status.GetPublishedNodeIds()[0], *wantVol.LinodeID) | ||
} | ||
} | ||
}) | ||
|
||
} | ||
|
||
} | ||
|
||
type fakeLinodeClient struct { | ||
volumes []linodego.Volume | ||
throwErr bool | ||
} | ||
|
||
func (flc *fakeLinodeClient) ListInstances(context.Context, *linodego.ListOptions) ([]linodego.Instance, error) { | ||
return nil, nil | ||
} | ||
func (flc *fakeLinodeClient) ListVolumes(context.Context, *linodego.ListOptions) ([]linodego.Volume, error) { | ||
if flc.throwErr { | ||
return nil, errors.New("sad times mate") | ||
} | ||
return flc.volumes, nil | ||
} | ||
func (flc *fakeLinodeClient) GetInstance(context.Context, int) (*linodego.Instance, error) { | ||
return nil, nil | ||
} | ||
func (flc *fakeLinodeClient) GetVolume(context.Context, int) (*linodego.Volume, error) { | ||
return nil, nil | ||
} | ||
func (flc *fakeLinodeClient) CreateVolume(context.Context, linodego.VolumeCreateOptions) (*linodego.Volume, error) { | ||
return nil, nil | ||
} | ||
func (flc *fakeLinodeClient) CloneVolume(context.Context, int, string) (*linodego.Volume, error) { | ||
return nil, nil | ||
} | ||
func (flc *fakeLinodeClient) AttachVolume(context.Context, int, *linodego.VolumeAttachOptions) (*linodego.Volume, error) { | ||
return nil, nil | ||
} | ||
func (flc *fakeLinodeClient) DetachVolume(context.Context, int) error { return nil } | ||
func (flc *fakeLinodeClient) WaitForVolumeLinodeID(context.Context, int, *int, int) (*linodego.Volume, error) { | ||
return nil, nil | ||
} | ||
func (flc *fakeLinodeClient) WaitForVolumeStatus(context.Context, int, linodego.VolumeStatus, int) (*linodego.Volume, error) { | ||
return nil, nil | ||
} | ||
func (flc *fakeLinodeClient) DeleteVolume(context.Context, int) error { return nil } | ||
func (flc *fakeLinodeClient) ResizeVolume(context.Context, int, int) error { return nil } | ||
func (flc *fakeLinodeClient) NewEventPoller(context.Context, any, linodego.EntityType, linodego.EventAction) (*linodego.EventPoller, error) { | ||
return nil, nil | ||
} | ||
|
||
func createLinodeID(i int) *int { | ||
return &i | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.