mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-29 21:01:02 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1096 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			1096 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /*
 | |
| Copyright 2017 The Kubernetes Authors.
 | |
| 
 | |
| Licensed under the Apache License, Version 2.0 (the "License");
 | |
| you may not use this file except in compliance with the License.
 | |
| You may obtain a copy of the License at
 | |
| 
 | |
|     http://www.apache.org/licenses/LICENSE-2.0
 | |
| 
 | |
| Unless required by applicable law or agreed to in writing, software
 | |
| distributed under the License is distributed on an "AS IS" BASIS,
 | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| See the License for the specific language governing permissions and
 | |
| limitations under the License.
 | |
| */
 | |
| 
 | |
| package csi
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"errors"
 | |
| 	"io"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"reflect"
 | |
| 	"strconv"
 | |
| 	"testing"
 | |
| 
 | |
| 	csipbv1 "github.com/container-storage-interface/spec/lib/go/csi"
 | |
| 	"github.com/stretchr/testify/assert"
 | |
| 
 | |
| 	api "k8s.io/api/core/v1"
 | |
| 	"k8s.io/apimachinery/pkg/api/resource"
 | |
| 	utilfeature "k8s.io/apiserver/pkg/util/feature"
 | |
| 	utiltesting "k8s.io/client-go/util/testing"
 | |
| 	featuregatetesting "k8s.io/component-base/featuregate/testing"
 | |
| 	"k8s.io/kubernetes/pkg/features"
 | |
| 	"k8s.io/kubernetes/pkg/volume"
 | |
| 	"k8s.io/kubernetes/pkg/volume/csi/fake"
 | |
| 	volumetypes "k8s.io/kubernetes/pkg/volume/util/types"
 | |
| )
 | |
| 
 | |
| type fakeCsiDriverClient struct {
 | |
| 	t          *testing.T
 | |
| 	nodeClient *fake.NodeClient
 | |
| }
 | |
| 
 | |
| func newFakeCsiDriverClient(t *testing.T, stagingCapable bool) *fakeCsiDriverClient {
 | |
| 	return &fakeCsiDriverClient{
 | |
| 		t:          t,
 | |
| 		nodeClient: fake.NewNodeClient(stagingCapable),
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func newFakeCsiDriverClientWithExpansion(t *testing.T, stagingCapable bool, expansionSet bool) *fakeCsiDriverClient {
 | |
| 	return &fakeCsiDriverClient{
 | |
| 		t:          t,
 | |
| 		nodeClient: fake.NewNodeClientWithExpansion(stagingCapable, expansionSet),
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func newFakeCsiDriverClientWithVolumeStats(t *testing.T, volumeStatsSet bool) *fakeCsiDriverClient {
 | |
| 	return &fakeCsiDriverClient{
 | |
| 		t:          t,
 | |
| 		nodeClient: fake.NewNodeClientWithVolumeStats(volumeStatsSet),
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func newFakeCsiDriverClientWithVolumeStatsAndCondition(t *testing.T, volumeStatsSet, volumeConditionSet bool) *fakeCsiDriverClient {
 | |
| 	return &fakeCsiDriverClient{
 | |
| 		t:          t,
 | |
| 		nodeClient: fake.NewNodeClientWithVolumeStatsAndCondition(volumeStatsSet, volumeConditionSet),
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func newFakeCsiDriverClientWithVolumeMountGroup(t *testing.T, stagingCapable, volumeMountGroupSet bool) *fakeCsiDriverClient {
 | |
| 	return &fakeCsiDriverClient{
 | |
| 		t:          t,
 | |
| 		nodeClient: fake.NewNodeClientWithVolumeMountGroup(stagingCapable, volumeMountGroupSet),
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (c *fakeCsiDriverClient) NodeGetInfo(ctx context.Context) (
 | |
| 	nodeID string,
 | |
| 	maxVolumePerNode int64,
 | |
| 	accessibleTopology map[string]string,
 | |
| 	err error) {
 | |
| 	resp, err := c.nodeClient.NodeGetInfo(ctx, &csipbv1.NodeGetInfoRequest{})
 | |
| 	topology := resp.GetAccessibleTopology()
 | |
| 	if topology != nil {
 | |
| 		accessibleTopology = topology.Segments
 | |
| 	}
 | |
| 	return resp.GetNodeId(), resp.GetMaxVolumesPerNode(), accessibleTopology, err
 | |
| }
 | |
| 
 | |
| func (c *fakeCsiDriverClient) NodeGetVolumeStats(ctx context.Context, volID string, targetPath string) (
 | |
| 	usageCountMap *volume.Metrics, err error) {
 | |
| 	c.t.Log("calling fake.NodeGetVolumeStats...")
 | |
| 	req := &csipbv1.NodeGetVolumeStatsRequest{
 | |
| 		VolumeId:   volID,
 | |
| 		VolumePath: targetPath,
 | |
| 	}
 | |
| 
 | |
| 	c.nodeClient.SetNodeVolumeStatsResp(getRawVolumeInfo())
 | |
| 	resp, err := c.nodeClient.NodeGetVolumeStats(ctx, req)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	usages := resp.GetUsage()
 | |
| 	if usages == nil {
 | |
| 		return nil, nil
 | |
| 	}
 | |
| 
 | |
| 	metrics := &volume.Metrics{}
 | |
| 
 | |
| 	isSupportNodeVolumeCondition, err := c.nodeSupportsVolumeCondition(ctx)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	if utilfeature.DefaultFeatureGate.Enabled(features.CSIVolumeHealth) && isSupportNodeVolumeCondition {
 | |
| 		abnormal, message := resp.VolumeCondition.GetAbnormal(), resp.VolumeCondition.GetMessage()
 | |
| 		metrics.Abnormal, metrics.Message = &abnormal, &message
 | |
| 	}
 | |
| 
 | |
| 	for _, usage := range usages {
 | |
| 		if usage == nil {
 | |
| 			continue
 | |
| 		}
 | |
| 		unit := usage.GetUnit()
 | |
| 		switch unit {
 | |
| 		case csipbv1.VolumeUsage_BYTES:
 | |
| 			metrics.Available = resource.NewQuantity(usage.GetAvailable(), resource.BinarySI)
 | |
| 			metrics.Capacity = resource.NewQuantity(usage.GetTotal(), resource.BinarySI)
 | |
| 			metrics.Used = resource.NewQuantity(usage.GetUsed(), resource.BinarySI)
 | |
| 		case csipbv1.VolumeUsage_INODES:
 | |
| 			metrics.InodesFree = resource.NewQuantity(usage.GetAvailable(), resource.BinarySI)
 | |
| 			metrics.Inodes = resource.NewQuantity(usage.GetTotal(), resource.BinarySI)
 | |
| 			metrics.InodesUsed = resource.NewQuantity(usage.GetUsed(), resource.BinarySI)
 | |
| 		}
 | |
| 	}
 | |
| 	return metrics, nil
 | |
| }
 | |
| 
 | |
| func (c *fakeCsiDriverClient) NodeSupportsVolumeStats(ctx context.Context) (bool, error) {
 | |
| 	c.t.Log("calling fake.NodeSupportsVolumeStats...")
 | |
| 	return c.nodeSupportsCapability(ctx, csipbv1.NodeServiceCapability_RPC_GET_VOLUME_STATS)
 | |
| }
 | |
| 
 | |
| func (c *fakeCsiDriverClient) NodePublishVolume(
 | |
| 	ctx context.Context,
 | |
| 	volID string,
 | |
| 	readOnly bool,
 | |
| 	stagingTargetPath string,
 | |
| 	targetPath string,
 | |
| 	accessMode api.PersistentVolumeAccessMode,
 | |
| 	publishContext map[string]string,
 | |
| 	volumeContext map[string]string,
 | |
| 	secrets map[string]string,
 | |
| 	fsType string,
 | |
| 	mountOptions []string,
 | |
| 	fsGroup *int64,
 | |
| ) error {
 | |
| 	c.t.Log("calling fake.NodePublishVolume...")
 | |
| 	req := &csipbv1.NodePublishVolumeRequest{
 | |
| 		VolumeId:          volID,
 | |
| 		TargetPath:        targetPath,
 | |
| 		StagingTargetPath: stagingTargetPath,
 | |
| 		Readonly:          readOnly,
 | |
| 		PublishContext:    publishContext,
 | |
| 		VolumeContext:     volumeContext,
 | |
| 		Secrets:           secrets,
 | |
| 		VolumeCapability: &csipbv1.VolumeCapability{
 | |
| 			AccessMode: &csipbv1.VolumeCapability_AccessMode{
 | |
| 				Mode: asCSIAccessModeV1(accessMode),
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	if fsType == fsTypeBlockName {
 | |
| 		req.VolumeCapability.AccessType = &csipbv1.VolumeCapability_Block{
 | |
| 			Block: &csipbv1.VolumeCapability_BlockVolume{},
 | |
| 		}
 | |
| 	} else {
 | |
| 		mountVolume := &csipbv1.VolumeCapability_MountVolume{
 | |
| 			FsType:     fsType,
 | |
| 			MountFlags: mountOptions,
 | |
| 		}
 | |
| 		if fsGroup != nil {
 | |
| 			mountVolume.VolumeMountGroup = strconv.FormatInt(*fsGroup, 10 /* base */)
 | |
| 		}
 | |
| 		req.VolumeCapability.AccessType = &csipbv1.VolumeCapability_Mount{
 | |
| 			Mount: mountVolume,
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	_, err := c.nodeClient.NodePublishVolume(ctx, req)
 | |
| 	if err != nil && !isFinalError(err) {
 | |
| 		return volumetypes.NewUncertainProgressError(err.Error())
 | |
| 	}
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| func (c *fakeCsiDriverClient) NodeUnpublishVolume(ctx context.Context, volID string, targetPath string) error {
 | |
| 	c.t.Log("calling fake.NodeUnpublishVolume...")
 | |
| 	req := &csipbv1.NodeUnpublishVolumeRequest{
 | |
| 		VolumeId:   volID,
 | |
| 		TargetPath: targetPath,
 | |
| 	}
 | |
| 
 | |
| 	_, err := c.nodeClient.NodeUnpublishVolume(ctx, req)
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| func (c *fakeCsiDriverClient) NodeStageVolume(ctx context.Context,
 | |
| 	volID string,
 | |
| 	publishContext map[string]string,
 | |
| 	stagingTargetPath string,
 | |
| 	fsType string,
 | |
| 	accessMode api.PersistentVolumeAccessMode,
 | |
| 	secrets map[string]string,
 | |
| 	volumeContext map[string]string,
 | |
| 	mountOptions []string,
 | |
| 	fsGroup *int64,
 | |
| ) error {
 | |
| 	c.t.Log("calling fake.NodeStageVolume...")
 | |
| 	req := &csipbv1.NodeStageVolumeRequest{
 | |
| 		VolumeId:          volID,
 | |
| 		PublishContext:    publishContext,
 | |
| 		StagingTargetPath: stagingTargetPath,
 | |
| 		VolumeCapability: &csipbv1.VolumeCapability{
 | |
| 			AccessMode: &csipbv1.VolumeCapability_AccessMode{
 | |
| 				Mode: asCSIAccessModeV1(accessMode),
 | |
| 			},
 | |
| 		},
 | |
| 		Secrets:       secrets,
 | |
| 		VolumeContext: volumeContext,
 | |
| 	}
 | |
| 	if fsType == fsTypeBlockName {
 | |
| 		req.VolumeCapability.AccessType = &csipbv1.VolumeCapability_Block{
 | |
| 			Block: &csipbv1.VolumeCapability_BlockVolume{},
 | |
| 		}
 | |
| 	} else {
 | |
| 		mountVolume := &csipbv1.VolumeCapability_MountVolume{
 | |
| 			FsType:     fsType,
 | |
| 			MountFlags: mountOptions,
 | |
| 		}
 | |
| 		if fsGroup != nil {
 | |
| 			mountVolume.VolumeMountGroup = strconv.FormatInt(*fsGroup, 10 /* base */)
 | |
| 		}
 | |
| 		req.VolumeCapability.AccessType = &csipbv1.VolumeCapability_Mount{
 | |
| 			Mount: mountVolume,
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	_, err := c.nodeClient.NodeStageVolume(ctx, req)
 | |
| 	if err != nil && !isFinalError(err) {
 | |
| 		return volumetypes.NewUncertainProgressError(err.Error())
 | |
| 	}
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| func (c *fakeCsiDriverClient) NodeUnstageVolume(ctx context.Context, volID, stagingTargetPath string) error {
 | |
| 	c.t.Log("calling fake.NodeUnstageVolume...")
 | |
| 	req := &csipbv1.NodeUnstageVolumeRequest{
 | |
| 		VolumeId:          volID,
 | |
| 		StagingTargetPath: stagingTargetPath,
 | |
| 	}
 | |
| 	_, err := c.nodeClient.NodeUnstageVolume(ctx, req)
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| func (c *fakeCsiDriverClient) NodeSupportsNodeExpand(ctx context.Context) (bool, error) {
 | |
| 	c.t.Log("calling fake.NodeSupportsNodeExpand...")
 | |
| 	return c.nodeSupportsCapability(ctx, csipbv1.NodeServiceCapability_RPC_EXPAND_VOLUME)
 | |
| }
 | |
| 
 | |
| func (c *fakeCsiDriverClient) NodeSupportsStageUnstage(ctx context.Context) (bool, error) {
 | |
| 	c.t.Log("calling fake.NodeGetCapabilities for NodeSupportsStageUnstage...")
 | |
| 	return c.nodeSupportsCapability(ctx, csipbv1.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME)
 | |
| }
 | |
| 
 | |
| func (c *fakeCsiDriverClient) NodeSupportsVolumeMountGroup(ctx context.Context) (bool, error) {
 | |
| 	c.t.Log("calling fake.NodeGetCapabilities for NodeSupportsVolumeMountGroup...")
 | |
| 	req := &csipbv1.NodeGetCapabilitiesRequest{}
 | |
| 	resp, err := c.nodeClient.NodeGetCapabilities(ctx, req)
 | |
| 	if err != nil {
 | |
| 		return false, err
 | |
| 	}
 | |
| 
 | |
| 	capabilities := resp.GetCapabilities()
 | |
| 
 | |
| 	volumeMountGroupSet := false
 | |
| 	if capabilities == nil {
 | |
| 		return false, nil
 | |
| 	}
 | |
| 	for _, capability := range capabilities {
 | |
| 		if capability.GetRpc().GetType() == csipbv1.NodeServiceCapability_RPC_VOLUME_MOUNT_GROUP {
 | |
| 			volumeMountGroupSet = true
 | |
| 		}
 | |
| 	}
 | |
| 	return volumeMountGroupSet, nil
 | |
| }
 | |
| 
 | |
| func (c *fakeCsiDriverClient) NodeExpandVolume(ctx context.Context, opts csiResizeOptions) (resource.Quantity, error) {
 | |
| 	c.t.Log("calling fake.NodeExpandVolume")
 | |
| 	req := &csipbv1.NodeExpandVolumeRequest{
 | |
| 		VolumeId:          opts.volumeID,
 | |
| 		VolumePath:        opts.volumePath,
 | |
| 		StagingTargetPath: opts.stagingTargetPath,
 | |
| 		CapacityRange:     &csipbv1.CapacityRange{RequiredBytes: opts.newSize.Value()},
 | |
| 		VolumeCapability: &csipbv1.VolumeCapability{
 | |
| 			AccessMode: &csipbv1.VolumeCapability_AccessMode{
 | |
| 				Mode: asCSIAccessModeV1(opts.accessMode),
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 	if opts.fsType == fsTypeBlockName {
 | |
| 		req.VolumeCapability.AccessType = &csipbv1.VolumeCapability_Block{
 | |
| 			Block: &csipbv1.VolumeCapability_BlockVolume{},
 | |
| 		}
 | |
| 	} else {
 | |
| 		req.VolumeCapability.AccessType = &csipbv1.VolumeCapability_Mount{
 | |
| 			Mount: &csipbv1.VolumeCapability_MountVolume{
 | |
| 				FsType:     opts.fsType,
 | |
| 				MountFlags: opts.mountOptions,
 | |
| 			},
 | |
| 		}
 | |
| 	}
 | |
| 	resp, err := c.nodeClient.NodeExpandVolume(ctx, req)
 | |
| 	if err != nil {
 | |
| 		return opts.newSize, err
 | |
| 	}
 | |
| 	updatedQuantity := resource.NewQuantity(resp.CapacityBytes, resource.BinarySI)
 | |
| 	return *updatedQuantity, nil
 | |
| }
 | |
| 
 | |
| func (c *fakeCsiDriverClient) nodeSupportsVolumeCondition(ctx context.Context) (bool, error) {
 | |
| 	c.t.Log("calling fake.nodeSupportsVolumeCondition...")
 | |
| 	return c.nodeSupportsCapability(ctx, csipbv1.NodeServiceCapability_RPC_VOLUME_CONDITION)
 | |
| }
 | |
| 
 | |
| func (c *fakeCsiDriverClient) NodeSupportsSingleNodeMultiWriterAccessMode(ctx context.Context) (bool, error) {
 | |
| 	c.t.Log("calling fake.NodeSupportsSingleNodeMultiWriterAccessMode...")
 | |
| 	return c.nodeSupportsCapability(ctx, csipbv1.NodeServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER)
 | |
| }
 | |
| 
 | |
| func (c *fakeCsiDriverClient) nodeSupportsCapability(ctx context.Context, capabilityType csipbv1.NodeServiceCapability_RPC_Type) (bool, error) {
 | |
| 	capabilities, err := c.nodeGetCapabilities(ctx)
 | |
| 	if err != nil {
 | |
| 		return false, err
 | |
| 	}
 | |
| 
 | |
| 	for _, capability := range capabilities {
 | |
| 		if capability.GetRpc().GetType() == capabilityType {
 | |
| 			return true, nil
 | |
| 		}
 | |
| 	}
 | |
| 	return false, nil
 | |
| }
 | |
| 
 | |
| func (c *fakeCsiDriverClient) nodeGetCapabilities(ctx context.Context) ([]*csipbv1.NodeServiceCapability, error) {
 | |
| 	req := &csipbv1.NodeGetCapabilitiesRequest{}
 | |
| 	resp, err := c.nodeClient.NodeGetCapabilities(ctx, req)
 | |
| 	if err != nil {
 | |
| 		return []*csipbv1.NodeServiceCapability{}, err
 | |
| 	}
 | |
| 	return resp.GetCapabilities(), nil
 | |
| }
 | |
| 
 | |
| func setupClient(t *testing.T, stageUnstageSet bool) csiClient {
 | |
| 	return newFakeCsiDriverClient(t, stageUnstageSet)
 | |
| }
 | |
| 
 | |
| func setupClientWithExpansion(t *testing.T, stageUnstageSet bool, expansionSet bool) csiClient {
 | |
| 	return newFakeCsiDriverClientWithExpansion(t, stageUnstageSet, expansionSet)
 | |
| }
 | |
| 
 | |
| func setupClientWithVolumeStatsAndCondition(t *testing.T, volumeStatsSet, volumeConditionSet bool) csiClient {
 | |
| 	return newFakeCsiDriverClientWithVolumeStatsAndCondition(t, volumeStatsSet, volumeConditionSet)
 | |
| }
 | |
| 
 | |
| func setupClientWithVolumeStats(t *testing.T, volumeStatsSet bool) csiClient {
 | |
| 	return newFakeCsiDriverClientWithVolumeStats(t, volumeStatsSet)
 | |
| }
 | |
| 
 | |
| func setupClientWithVolumeMountGroup(t *testing.T, stageUnstageSet bool, volumeMountGroupSet bool) csiClient {
 | |
| 	return newFakeCsiDriverClientWithVolumeMountGroup(t, stageUnstageSet, volumeMountGroupSet)
 | |
| }
 | |
| 
 | |
| func checkErr(t *testing.T, expectedAnError bool, actualError error) {
 | |
| 	t.Helper()
 | |
| 
 | |
| 	errOccurred := actualError != nil
 | |
| 
 | |
| 	if expectedAnError && !errOccurred {
 | |
| 		t.Error("expected an error")
 | |
| 	}
 | |
| 
 | |
| 	if !expectedAnError && errOccurred {
 | |
| 		t.Errorf("expected no error, got: %v", actualError)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestClientNodeGetInfo(t *testing.T) {
 | |
| 	testCases := []struct {
 | |
| 		name                       string
 | |
| 		expectedNodeID             string
 | |
| 		expectedMaxVolumePerNode   int64
 | |
| 		expectedAccessibleTopology map[string]string
 | |
| 		mustFail                   bool
 | |
| 		err                        error
 | |
| 	}{
 | |
| 		{
 | |
| 			name:                       "test ok",
 | |
| 			expectedNodeID:             "node1",
 | |
| 			expectedMaxVolumePerNode:   16,
 | |
| 			expectedAccessibleTopology: map[string]string{"com.example.csi-topology/zone": "zone1"},
 | |
| 		},
 | |
| 		{
 | |
| 			name:     "grpc error",
 | |
| 			mustFail: true,
 | |
| 			err:      errors.New("grpc error"),
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tc := range testCases {
 | |
| 		t.Logf("test case: %s", tc.name)
 | |
| 
 | |
| 		fakeCloser := fake.NewCloser(t)
 | |
| 		client := &csiDriverClient{
 | |
| 			driverName: "Fake Driver Name",
 | |
| 			nodeV1ClientCreator: func(addr csiAddr, m *MetricsManager) (csipbv1.NodeClient, io.Closer, error) {
 | |
| 				nodeClient := fake.NewNodeClient(false /* stagingCapable */)
 | |
| 				nodeClient.SetNextError(tc.err)
 | |
| 				nodeClient.SetNodeGetInfoResp(&csipbv1.NodeGetInfoResponse{
 | |
| 					NodeId:            tc.expectedNodeID,
 | |
| 					MaxVolumesPerNode: tc.expectedMaxVolumePerNode,
 | |
| 					AccessibleTopology: &csipbv1.Topology{
 | |
| 						Segments: tc.expectedAccessibleTopology,
 | |
| 					},
 | |
| 				})
 | |
| 				return nodeClient, fakeCloser, nil
 | |
| 			},
 | |
| 		}
 | |
| 
 | |
| 		nodeID, maxVolumePerNode, accessibleTopology, err := client.NodeGetInfo(context.Background())
 | |
| 		checkErr(t, tc.mustFail, err)
 | |
| 
 | |
| 		if nodeID != tc.expectedNodeID {
 | |
| 			t.Errorf("expected nodeID: %v; got: %v", tc.expectedNodeID, nodeID)
 | |
| 		}
 | |
| 
 | |
| 		if maxVolumePerNode != tc.expectedMaxVolumePerNode {
 | |
| 			t.Errorf("expected maxVolumePerNode: %v; got: %v", tc.expectedMaxVolumePerNode, maxVolumePerNode)
 | |
| 		}
 | |
| 
 | |
| 		if !reflect.DeepEqual(accessibleTopology, tc.expectedAccessibleTopology) {
 | |
| 			t.Errorf("expected accessibleTopology: %v; got: %v", tc.expectedAccessibleTopology, accessibleTopology)
 | |
| 		}
 | |
| 
 | |
| 		if !tc.mustFail {
 | |
| 			fakeCloser.Check()
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestClientNodePublishVolume(t *testing.T) {
 | |
| 	var testFSGroup int64 = 3000
 | |
| 
 | |
| 	tmpDir, err := utiltesting.MkTmpdir("csi-test")
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("can't create temp dir: %v", err)
 | |
| 	}
 | |
| 	defer os.RemoveAll(tmpDir)
 | |
| 	testPath := filepath.Join(tmpDir, "path")
 | |
| 
 | |
| 	testCases := []struct {
 | |
| 		name                     string
 | |
| 		volID                    string
 | |
| 		targetPath               string
 | |
| 		fsType                   string
 | |
| 		fsGroup                  *int64
 | |
| 		expectedVolumeMountGroup string
 | |
| 		mustFail                 bool
 | |
| 		err                      error
 | |
| 	}{
 | |
| 		{name: "test ok", volID: "vol-test", targetPath: testPath},
 | |
| 		{name: "missing volID", targetPath: testPath, mustFail: true},
 | |
| 		{name: "missing target path", volID: "vol-test", mustFail: true},
 | |
| 		{name: "bad fs", volID: "vol-test", targetPath: testPath, fsType: "badfs", mustFail: true},
 | |
| 		{name: "grpc error", volID: "vol-test", targetPath: testPath, mustFail: true, err: errors.New("grpc error")},
 | |
| 		{name: "fsgroup", volID: "vol-test", targetPath: testPath, fsGroup: &testFSGroup, expectedVolumeMountGroup: "3000"},
 | |
| 	}
 | |
| 
 | |
| 	for _, tc := range testCases {
 | |
| 		t.Logf("test case: %s", tc.name)
 | |
| 
 | |
| 		nodeClient := fake.NewNodeClient(false /* stagingCapable */)
 | |
| 		nodeClient.SetNextError(tc.err)
 | |
| 		fakeCloser := fake.NewCloser(t)
 | |
| 		client := &csiDriverClient{
 | |
| 			driverName: "Fake Driver Name",
 | |
| 			nodeV1ClientCreator: func(addr csiAddr, m *MetricsManager) (csipbv1.NodeClient, io.Closer, error) {
 | |
| 				return nodeClient, fakeCloser, nil
 | |
| 			},
 | |
| 		}
 | |
| 
 | |
| 		err := client.NodePublishVolume(
 | |
| 			context.Background(),
 | |
| 			tc.volID,
 | |
| 			false,
 | |
| 			"",
 | |
| 			tc.targetPath,
 | |
| 			api.ReadWriteOnce,
 | |
| 			map[string]string{"device": "/dev/null"},
 | |
| 			map[string]string{"attr0": "val0"},
 | |
| 			map[string]string{},
 | |
| 			tc.fsType,
 | |
| 			[]string{},
 | |
| 			tc.fsGroup,
 | |
| 		)
 | |
| 		checkErr(t, tc.mustFail, err)
 | |
| 
 | |
| 		volumeMountGroup := nodeClient.GetNodePublishedVolumes()[tc.volID].VolumeMountGroup
 | |
| 		if volumeMountGroup != tc.expectedVolumeMountGroup {
 | |
| 			t.Errorf("Expected VolumeMountGroup in NodePublishVolumeRequest to be %q, got: %q", tc.expectedVolumeMountGroup, volumeMountGroup)
 | |
| 		}
 | |
| 
 | |
| 		if !tc.mustFail {
 | |
| 			fakeCloser.Check()
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestClientNodeUnpublishVolume(t *testing.T) {
 | |
| 	tmpDir, err := utiltesting.MkTmpdir("csi-test")
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("can't create temp dir: %v", err)
 | |
| 	}
 | |
| 	defer os.RemoveAll(tmpDir)
 | |
| 	testPath := filepath.Join(tmpDir, "path")
 | |
| 
 | |
| 	testCases := []struct {
 | |
| 		name       string
 | |
| 		volID      string
 | |
| 		targetPath string
 | |
| 		mustFail   bool
 | |
| 		err        error
 | |
| 	}{
 | |
| 		{name: "test ok", volID: "vol-test", targetPath: testPath},
 | |
| 		{name: "missing volID", targetPath: testPath, mustFail: true},
 | |
| 		{name: "missing target path", volID: testPath, mustFail: true},
 | |
| 		{name: "grpc error", volID: "vol-test", targetPath: testPath, mustFail: true, err: errors.New("grpc error")},
 | |
| 	}
 | |
| 
 | |
| 	for _, tc := range testCases {
 | |
| 		t.Logf("test case: %s", tc.name)
 | |
| 		fakeCloser := fake.NewCloser(t)
 | |
| 		client := &csiDriverClient{
 | |
| 			driverName: "Fake Driver Name",
 | |
| 			nodeV1ClientCreator: func(addr csiAddr, m *MetricsManager) (csipbv1.NodeClient, io.Closer, error) {
 | |
| 				nodeClient := fake.NewNodeClient(false /* stagingCapable */)
 | |
| 				nodeClient.SetNextError(tc.err)
 | |
| 				return nodeClient, fakeCloser, nil
 | |
| 			},
 | |
| 		}
 | |
| 
 | |
| 		err := client.NodeUnpublishVolume(context.Background(), tc.volID, tc.targetPath)
 | |
| 		checkErr(t, tc.mustFail, err)
 | |
| 
 | |
| 		if !tc.mustFail {
 | |
| 			fakeCloser.Check()
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestClientNodeStageVolume(t *testing.T) {
 | |
| 	var testFSGroup int64 = 3000
 | |
| 
 | |
| 	tmpDir, err := utiltesting.MkTmpdir("csi-test")
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("can't create temp dir: %v", err)
 | |
| 	}
 | |
| 	defer os.RemoveAll(tmpDir)
 | |
| 	testPath := filepath.Join(tmpDir, "/test/path")
 | |
| 
 | |
| 	testCases := []struct {
 | |
| 		name                     string
 | |
| 		volID                    string
 | |
| 		stagingTargetPath        string
 | |
| 		fsType                   string
 | |
| 		secrets                  map[string]string
 | |
| 		mountOptions             []string
 | |
| 		fsGroup                  *int64
 | |
| 		expectedVolumeMountGroup string
 | |
| 		mustFail                 bool
 | |
| 		err                      error
 | |
| 	}{
 | |
| 		{name: "test ok", volID: "vol-test", stagingTargetPath: testPath, fsType: "ext4", mountOptions: []string{"unvalidated"}},
 | |
| 		{name: "missing volID", stagingTargetPath: testPath, mustFail: true},
 | |
| 		{name: "missing target path", volID: "vol-test", mustFail: true},
 | |
| 		{name: "bad fs", volID: "vol-test", stagingTargetPath: testPath, fsType: "badfs", mustFail: true},
 | |
| 		{name: "grpc error", volID: "vol-test", stagingTargetPath: testPath, mustFail: true, err: errors.New("grpc error")},
 | |
| 		{name: "fsgroup", volID: "vol-test", stagingTargetPath: testPath, fsGroup: &testFSGroup, expectedVolumeMountGroup: "3000"},
 | |
| 	}
 | |
| 
 | |
| 	for _, tc := range testCases {
 | |
| 		t.Logf("Running test case: %s", tc.name)
 | |
| 
 | |
| 		nodeClient := fake.NewNodeClientWithVolumeMountGroup(true /* stagingCapable */, true /* volumeMountGroupCapable */)
 | |
| 		nodeClient.SetNextError(tc.err)
 | |
| 		fakeCloser := fake.NewCloser(t)
 | |
| 		client := &csiDriverClient{
 | |
| 			driverName: "Fake Driver Name",
 | |
| 			nodeV1ClientCreator: func(addr csiAddr, m *MetricsManager) (csipbv1.NodeClient, io.Closer, error) {
 | |
| 				return nodeClient, fakeCloser, nil
 | |
| 			},
 | |
| 		}
 | |
| 
 | |
| 		err := client.NodeStageVolume(
 | |
| 			context.Background(),
 | |
| 			tc.volID,
 | |
| 			map[string]string{"device": "/dev/null"},
 | |
| 			tc.stagingTargetPath,
 | |
| 			tc.fsType,
 | |
| 			api.ReadWriteOnce,
 | |
| 			tc.secrets,
 | |
| 			map[string]string{"attr0": "val0"},
 | |
| 			tc.mountOptions,
 | |
| 			tc.fsGroup,
 | |
| 		)
 | |
| 		checkErr(t, tc.mustFail, err)
 | |
| 
 | |
| 		volumeMountGroup := nodeClient.GetNodeStagedVolumes()[tc.volID].VolumeMountGroup
 | |
| 		if volumeMountGroup != tc.expectedVolumeMountGroup {
 | |
| 			t.Errorf("expected VolumeMountGroup parameter in NodePublishVolumeRequest to be %q, got: %q", tc.expectedVolumeMountGroup, volumeMountGroup)
 | |
| 		}
 | |
| 
 | |
| 		if !tc.mustFail {
 | |
| 			fakeCloser.Check()
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestClientNodeUnstageVolume(t *testing.T) {
 | |
| 	tmpDir, err := utiltesting.MkTmpdir("csi-test")
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("can't create temp dir: %v", err)
 | |
| 	}
 | |
| 	defer os.RemoveAll(tmpDir)
 | |
| 	testPath := filepath.Join(tmpDir, "/test/path")
 | |
| 
 | |
| 	testCases := []struct {
 | |
| 		name              string
 | |
| 		volID             string
 | |
| 		stagingTargetPath string
 | |
| 		mustFail          bool
 | |
| 		err               error
 | |
| 	}{
 | |
| 		{name: "test ok", volID: "vol-test", stagingTargetPath: testPath},
 | |
| 		{name: "missing volID", stagingTargetPath: testPath, mustFail: true},
 | |
| 		{name: "missing target path", volID: "vol-test", mustFail: true},
 | |
| 		{name: "grpc error", volID: "vol-test", stagingTargetPath: testPath, mustFail: true, err: errors.New("grpc error")},
 | |
| 	}
 | |
| 
 | |
| 	for _, tc := range testCases {
 | |
| 		t.Logf("Running test case: %s", tc.name)
 | |
| 		fakeCloser := fake.NewCloser(t)
 | |
| 		client := &csiDriverClient{
 | |
| 			driverName: "Fake Driver Name",
 | |
| 			nodeV1ClientCreator: func(addr csiAddr, m *MetricsManager) (csipbv1.NodeClient, io.Closer, error) {
 | |
| 				nodeClient := fake.NewNodeClient(false /* stagingCapable */)
 | |
| 				nodeClient.SetNextError(tc.err)
 | |
| 				return nodeClient, fakeCloser, nil
 | |
| 			},
 | |
| 		}
 | |
| 
 | |
| 		err := client.NodeUnstageVolume(
 | |
| 			context.Background(),
 | |
| 			tc.volID, tc.stagingTargetPath,
 | |
| 		)
 | |
| 		checkErr(t, tc.mustFail, err)
 | |
| 
 | |
| 		if !tc.mustFail {
 | |
| 			fakeCloser.Check()
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestClientNodeSupportsStageUnstage(t *testing.T) {
 | |
| 	testClientNodeSupportsCapabilities(t,
 | |
| 		func(client *csiDriverClient) (bool, error) {
 | |
| 			return client.NodeSupportsStageUnstage(context.Background())
 | |
| 		},
 | |
| 		func(stagingCapable bool) *fake.NodeClient {
 | |
| 			// Creates a staging-capable client
 | |
| 			return fake.NewNodeClient(stagingCapable)
 | |
| 		})
 | |
| }
 | |
| 
 | |
| func TestClientNodeSupportsNodeExpand(t *testing.T) {
 | |
| 	testClientNodeSupportsCapabilities(t,
 | |
| 		func(client *csiDriverClient) (bool, error) {
 | |
| 			return client.NodeSupportsNodeExpand(context.Background())
 | |
| 		},
 | |
| 		func(expansionCapable bool) *fake.NodeClient {
 | |
| 			return fake.NewNodeClientWithExpansion(false /* stageCapable */, expansionCapable)
 | |
| 		})
 | |
| }
 | |
| 
 | |
| func TestClientNodeSupportsVolumeStats(t *testing.T) {
 | |
| 	testClientNodeSupportsCapabilities(t,
 | |
| 		func(client *csiDriverClient) (bool, error) {
 | |
| 			return client.NodeSupportsVolumeStats(context.Background())
 | |
| 		},
 | |
| 		func(volumeStatsCapable bool) *fake.NodeClient {
 | |
| 			return fake.NewNodeClientWithVolumeStats(volumeStatsCapable)
 | |
| 		})
 | |
| }
 | |
| 
 | |
| func TestClientNodeSupportsVolumeMountGroup(t *testing.T) {
 | |
| 	testClientNodeSupportsCapabilities(t,
 | |
| 		func(client *csiDriverClient) (bool, error) {
 | |
| 			return client.NodeSupportsVolumeMountGroup(context.Background())
 | |
| 		},
 | |
| 		func(volumeMountGroupCapable bool) *fake.NodeClient {
 | |
| 			return fake.NewNodeClientWithVolumeMountGroup(false /* stagingCapable */, volumeMountGroupCapable)
 | |
| 		})
 | |
| }
 | |
| 
 | |
| func testClientNodeSupportsCapabilities(
 | |
| 	t *testing.T,
 | |
| 	capabilityMethodToTest func(*csiDriverClient) (bool, error),
 | |
| 	nodeClientGenerator func(bool) *fake.NodeClient) {
 | |
| 
 | |
| 	testCases := []struct {
 | |
| 		name    string
 | |
| 		capable bool
 | |
| 	}{
 | |
| 		{name: "positive", capable: true},
 | |
| 		{name: "negative", capable: false},
 | |
| 	}
 | |
| 
 | |
| 	for _, tc := range testCases {
 | |
| 		t.Logf("Running test case: %s", tc.name)
 | |
| 		fakeCloser := fake.NewCloser(t)
 | |
| 		client := &csiDriverClient{
 | |
| 			driverName: "Fake Driver Name",
 | |
| 			nodeV1ClientCreator: func(addr csiAddr, m *MetricsManager) (csipbv1.NodeClient, io.Closer, error) {
 | |
| 				nodeClient := nodeClientGenerator(tc.capable)
 | |
| 				return nodeClient, fakeCloser, nil
 | |
| 			},
 | |
| 		}
 | |
| 
 | |
| 		got, _ := capabilityMethodToTest(client)
 | |
| 
 | |
| 		if got != tc.capable {
 | |
| 			t.Errorf("Expected capability support to be %v, got: %v", tc.capable, got)
 | |
| 		}
 | |
| 
 | |
| 		fakeCloser.Check()
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestNodeExpandVolume(t *testing.T) {
 | |
| 	testCases := []struct {
 | |
| 		name       string
 | |
| 		volID      string
 | |
| 		volumePath string
 | |
| 		newSize    resource.Quantity
 | |
| 		mustFail   bool
 | |
| 		err        error
 | |
| 	}{
 | |
| 		{
 | |
| 			name:       "with all correct values",
 | |
| 			volID:      "vol-abcde",
 | |
| 			volumePath: "/foo/bar",
 | |
| 			newSize:    resource.MustParse("10Gi"),
 | |
| 			mustFail:   false,
 | |
| 		},
 | |
| 		{
 | |
| 			name:       "with missing volume-id",
 | |
| 			volumePath: "/foo/bar",
 | |
| 			newSize:    resource.MustParse("10Gi"),
 | |
| 			mustFail:   true,
 | |
| 		},
 | |
| 		{
 | |
| 			name:     "with missing volume path",
 | |
| 			volID:    "vol-1234",
 | |
| 			newSize:  resource.MustParse("10Gi"),
 | |
| 			mustFail: true,
 | |
| 		},
 | |
| 		{
 | |
| 			name:       "with invalid quantity",
 | |
| 			volID:      "vol-1234",
 | |
| 			volumePath: "/foo/bar",
 | |
| 			newSize:    *resource.NewQuantity(-10, resource.DecimalSI),
 | |
| 			mustFail:   true,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tc := range testCases {
 | |
| 		t.Logf("Running test cases : %s", tc.name)
 | |
| 		fakeCloser := fake.NewCloser(t)
 | |
| 		client := &csiDriverClient{
 | |
| 			driverName: "Fake Driver Name",
 | |
| 			nodeV1ClientCreator: func(addr csiAddr, m *MetricsManager) (csipbv1.NodeClient, io.Closer, error) {
 | |
| 				nodeClient := fake.NewNodeClient(false /* stagingCapable */)
 | |
| 				nodeClient.SetNextError(tc.err)
 | |
| 				return nodeClient, fakeCloser, nil
 | |
| 			},
 | |
| 		}
 | |
| 		opts := csiResizeOptions{volumeID: tc.volID, volumePath: tc.volumePath, newSize: tc.newSize}
 | |
| 		_, err := client.NodeExpandVolume(context.Background(), opts)
 | |
| 		checkErr(t, tc.mustFail, err)
 | |
| 		if !tc.mustFail {
 | |
| 			fakeCloser.Check()
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| type VolumeStatsOptions struct {
 | |
| 	VolumeSpec *volume.Spec
 | |
| 
 | |
| 	// this just could be volumeID
 | |
| 	VolumeID string
 | |
| 
 | |
| 	// DeviceMountPath location where device is mounted on the node. If volume type
 | |
| 	// is attachable - this would be global mount path otherwise
 | |
| 	// it would be location where volume was mounted for the pod
 | |
| 	DeviceMountPath string
 | |
| }
 | |
| 
 | |
| func TestVolumeHealthEnable(t *testing.T) {
 | |
| 	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIVolumeHealth, true)()
 | |
| 	spec := volume.NewSpecFromPersistentVolume(makeTestPV("test-pv", 10, "metrics", "test-vol"), false)
 | |
| 	tests := []struct {
 | |
| 		name               string
 | |
| 		volumeStatsSet     bool
 | |
| 		volumeConditionSet bool
 | |
| 		volumeData         VolumeStatsOptions
 | |
| 		success            bool
 | |
| 	}{
 | |
| 		{
 | |
| 			name:               "when nodeVolumeStats=on, VolumeID=on, DeviceMountPath=on, VolumeCondition=on",
 | |
| 			volumeStatsSet:     true,
 | |
| 			volumeConditionSet: true,
 | |
| 			volumeData: VolumeStatsOptions{
 | |
| 				VolumeSpec:      spec,
 | |
| 				VolumeID:        "volume1",
 | |
| 				DeviceMountPath: "/foo/bar",
 | |
| 			},
 | |
| 			success: true,
 | |
| 		},
 | |
| 		{
 | |
| 			name:               "when nodeVolumeStats=on, VolumeID=on, DeviceMountPath=on, VolumeCondition=off",
 | |
| 			volumeStatsSet:     true,
 | |
| 			volumeConditionSet: false,
 | |
| 			volumeData: VolumeStatsOptions{
 | |
| 				VolumeSpec:      spec,
 | |
| 				VolumeID:        "volume1",
 | |
| 				DeviceMountPath: "/foo/bar",
 | |
| 			},
 | |
| 			success: true,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tc := range tests {
 | |
| 		t.Run(tc.name, func(t *testing.T) {
 | |
| 			ctx, cancel := context.WithTimeout(context.Background(), csiTimeout)
 | |
| 			defer cancel()
 | |
| 			csiSource, _ := getCSISourceFromSpec(tc.volumeData.VolumeSpec)
 | |
| 			csClient := setupClientWithVolumeStatsAndCondition(t, tc.volumeStatsSet, tc.volumeConditionSet)
 | |
| 			metrics, err := csClient.NodeGetVolumeStats(ctx, csiSource.VolumeHandle, tc.volumeData.DeviceMountPath)
 | |
| 			if tc.success {
 | |
| 				assert.Nil(t, err)
 | |
| 			}
 | |
| 
 | |
| 			if metrics == nil {
 | |
| 				t.Errorf("csi.NodeGetVolumeStats returned nil metrics for volume %s", tc.volumeData.VolumeID)
 | |
| 			} else {
 | |
| 				if tc.volumeConditionSet {
 | |
| 					assert.NotNil(t, metrics.Abnormal)
 | |
| 					assert.NotNil(t, metrics.Message)
 | |
| 				} else {
 | |
| 					assert.Nil(t, metrics.Abnormal)
 | |
| 					assert.Nil(t, metrics.Message)
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestVolumeHealthDisable(t *testing.T) {
 | |
| 	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIVolumeHealth, false)()
 | |
| 	spec := volume.NewSpecFromPersistentVolume(makeTestPV("test-pv", 10, "metrics", "test-vol"), false)
 | |
| 	tests := []struct {
 | |
| 		name           string
 | |
| 		volumeStatsSet bool
 | |
| 		volumeData     VolumeStatsOptions
 | |
| 		success        bool
 | |
| 	}{
 | |
| 		{
 | |
| 			name:           "when nodeVolumeStats=on, VolumeID=on, DeviceMountPath=on, VolumeCondition=off",
 | |
| 			volumeStatsSet: true,
 | |
| 			volumeData: VolumeStatsOptions{
 | |
| 				VolumeSpec:      spec,
 | |
| 				VolumeID:        "volume1",
 | |
| 				DeviceMountPath: "/foo/bar",
 | |
| 			},
 | |
| 			success: true,
 | |
| 		},
 | |
| 	}
 | |
| 	for _, tc := range tests {
 | |
| 		t.Run(tc.name, func(t *testing.T) {
 | |
| 			ctx, cancel := context.WithTimeout(context.Background(), csiTimeout)
 | |
| 			defer cancel()
 | |
| 			csiSource, _ := getCSISourceFromSpec(tc.volumeData.VolumeSpec)
 | |
| 			csClient := setupClientWithVolumeStatsAndCondition(t, tc.volumeStatsSet, false)
 | |
| 			metrics, err := csClient.NodeGetVolumeStats(ctx, csiSource.VolumeHandle, tc.volumeData.DeviceMountPath)
 | |
| 			if tc.success {
 | |
| 				assert.Nil(t, err)
 | |
| 			}
 | |
| 
 | |
| 			if metrics == nil {
 | |
| 				t.Errorf("csi.NodeGetVolumeStats returned nil metrics for volume %s", tc.volumeData.VolumeID)
 | |
| 			} else {
 | |
| 				assert.Nil(t, metrics.Abnormal)
 | |
| 				assert.Nil(t, metrics.Message)
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestVolumeStats(t *testing.T) {
 | |
| 	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIVolumeHealth, true)()
 | |
| 	spec := volume.NewSpecFromPersistentVolume(makeTestPV("test-pv", 10, "metrics", "test-vol"), false)
 | |
| 	tests := []struct {
 | |
| 		name               string
 | |
| 		volumeStatsSet     bool
 | |
| 		volumeConditionSet bool
 | |
| 		volumeData         VolumeStatsOptions
 | |
| 		success            bool
 | |
| 	}{
 | |
| 		{
 | |
| 			name:           "when nodeVolumeStats=on, VolumeID=on, DeviceMountPath=on",
 | |
| 			volumeStatsSet: true,
 | |
| 			volumeData: VolumeStatsOptions{
 | |
| 				VolumeSpec:      spec,
 | |
| 				VolumeID:        "volume1",
 | |
| 				DeviceMountPath: "/foo/bar",
 | |
| 			},
 | |
| 			success: true,
 | |
| 		},
 | |
| 
 | |
| 		{
 | |
| 			name:           "when nodeVolumeStats=off, VolumeID=on, DeviceMountPath=on",
 | |
| 			volumeStatsSet: false,
 | |
| 			volumeData: VolumeStatsOptions{
 | |
| 				VolumeSpec:      spec,
 | |
| 				VolumeID:        "volume1",
 | |
| 				DeviceMountPath: "/foo/bar",
 | |
| 			},
 | |
| 			success: false,
 | |
| 		},
 | |
| 
 | |
| 		{
 | |
| 			name:           "when nodeVolumeStats=on, VolumeID=off, DeviceMountPath=on",
 | |
| 			volumeStatsSet: true,
 | |
| 			volumeData: VolumeStatsOptions{
 | |
| 				VolumeSpec:      spec,
 | |
| 				VolumeID:        "",
 | |
| 				DeviceMountPath: "/foo/bar",
 | |
| 			},
 | |
| 			success: false,
 | |
| 		},
 | |
| 
 | |
| 		{
 | |
| 			name:           "when nodeVolumeStats=on, VolumeID=on, DeviceMountPath=off",
 | |
| 			volumeStatsSet: true,
 | |
| 			volumeData: VolumeStatsOptions{
 | |
| 				VolumeSpec:      spec,
 | |
| 				VolumeID:        "volume1",
 | |
| 				DeviceMountPath: "",
 | |
| 			},
 | |
| 			success: false,
 | |
| 		},
 | |
| 		{
 | |
| 			name:           "when nodeVolumeStats=on, VolumeID=on, DeviceMountPath=off",
 | |
| 			volumeStatsSet: true,
 | |
| 			volumeData: VolumeStatsOptions{
 | |
| 				VolumeSpec:      spec,
 | |
| 				VolumeID:        "",
 | |
| 				DeviceMountPath: "",
 | |
| 			},
 | |
| 			success: false,
 | |
| 		},
 | |
| 	}
 | |
| 	for _, tc := range tests {
 | |
| 		t.Run(tc.name, func(t *testing.T) {
 | |
| 			ctx, cancel := context.WithTimeout(context.Background(), csiTimeout)
 | |
| 			defer cancel()
 | |
| 			csiSource, _ := getCSISourceFromSpec(tc.volumeData.VolumeSpec)
 | |
| 			csClient := setupClientWithVolumeStats(t, tc.volumeStatsSet)
 | |
| 			_, err := csClient.NodeGetVolumeStats(ctx, csiSource.VolumeHandle, tc.volumeData.DeviceMountPath)
 | |
| 			if err != nil && tc.success {
 | |
| 				t.Errorf("For %s : expected %v got %v", tc.name, tc.success, err)
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| 
 | |
| }
 | |
| 
 | |
| func TestAccessModeMapping(t *testing.T) {
 | |
| 	tests := []struct {
 | |
| 		name                     string
 | |
| 		singleNodeMultiWriterSet bool
 | |
| 		accessMode               api.PersistentVolumeAccessMode
 | |
| 		expectedMappedAccessMode csipbv1.VolumeCapability_AccessMode_Mode
 | |
| 	}{
 | |
| 		{
 | |
| 			name:                     "with ReadWriteOnce and incapable driver",
 | |
| 			singleNodeMultiWriterSet: false,
 | |
| 			accessMode:               api.ReadWriteOnce,
 | |
| 			expectedMappedAccessMode: csipbv1.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
 | |
| 		},
 | |
| 		{
 | |
| 			name:                     "with ReadOnlyMany and incapable driver",
 | |
| 			singleNodeMultiWriterSet: false,
 | |
| 			accessMode:               api.ReadOnlyMany,
 | |
| 			expectedMappedAccessMode: csipbv1.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY,
 | |
| 		},
 | |
| 		{
 | |
| 			name:                     "with ReadWriteMany and incapable driver",
 | |
| 			singleNodeMultiWriterSet: false,
 | |
| 			accessMode:               api.ReadWriteMany,
 | |
| 			expectedMappedAccessMode: csipbv1.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER,
 | |
| 		},
 | |
| 		{
 | |
| 			name:                     "with ReadWriteOncePod and incapable driver",
 | |
| 			singleNodeMultiWriterSet: false,
 | |
| 			accessMode:               api.ReadWriteOncePod,
 | |
| 			expectedMappedAccessMode: csipbv1.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
 | |
| 		},
 | |
| 		{
 | |
| 			name:                     "with ReadWriteOnce and capable driver",
 | |
| 			singleNodeMultiWriterSet: true,
 | |
| 			accessMode:               api.ReadWriteOnce,
 | |
| 			expectedMappedAccessMode: csipbv1.VolumeCapability_AccessMode_SINGLE_NODE_MULTI_WRITER,
 | |
| 		},
 | |
| 		{
 | |
| 			name:                     "with ReadOnlyMany and capable driver",
 | |
| 			singleNodeMultiWriterSet: true,
 | |
| 			accessMode:               api.ReadOnlyMany,
 | |
| 			expectedMappedAccessMode: csipbv1.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY,
 | |
| 		},
 | |
| 		{
 | |
| 			name:                     "with ReadWriteMany and capable driver",
 | |
| 			singleNodeMultiWriterSet: true,
 | |
| 			accessMode:               api.ReadWriteMany,
 | |
| 			expectedMappedAccessMode: csipbv1.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER,
 | |
| 		},
 | |
| 		{
 | |
| 			name:                     "with ReadWriteOncePod and capable driver",
 | |
| 			singleNodeMultiWriterSet: true,
 | |
| 			accessMode:               api.ReadWriteOncePod,
 | |
| 			expectedMappedAccessMode: csipbv1.VolumeCapability_AccessMode_SINGLE_NODE_SINGLE_WRITER,
 | |
| 		},
 | |
| 	}
 | |
| 	for _, tc := range tests {
 | |
| 		t.Run(tc.name, func(t *testing.T) {
 | |
| 			fakeCloser := fake.NewCloser(t)
 | |
| 			client := &csiDriverClient{
 | |
| 				driverName: "Fake Driver Name",
 | |
| 				nodeV1ClientCreator: func(addr csiAddr, m *MetricsManager) (csipbv1.NodeClient, io.Closer, error) {
 | |
| 					nodeClient := fake.NewNodeClientWithSingleNodeMultiWriter(tc.singleNodeMultiWriterSet)
 | |
| 					return nodeClient, fakeCloser, nil
 | |
| 				},
 | |
| 			}
 | |
| 
 | |
| 			accessModeMapper, err := client.getNodeV1AccessModeMapper(context.Background())
 | |
| 			if err != nil {
 | |
| 				t.Error(err)
 | |
| 			}
 | |
| 
 | |
| 			mappedAccessMode := accessModeMapper(tc.accessMode)
 | |
| 			if mappedAccessMode != tc.expectedMappedAccessMode {
 | |
| 				t.Errorf("expected access mode: %v; got: %v", tc.expectedMappedAccessMode, mappedAccessMode)
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 |