From 5744fbed2fcd170dc5e656b93dce20bfcbd4f592 Mon Sep 17 00:00:00 2001 From: jharrod Date: Mon, 4 Nov 2024 10:56:58 -0700 Subject: [PATCH] Filesystem code encapsulation Refactor filesystem utils into its own package --- config/config.go | 6 - core/orchestrator_core.go | 11 +- frontend/csi/controller_server.go | 3 +- .../csi/node_helpers/kubernetes/plugin.go | 7 +- frontend/csi/node_helpers/plain/plugin.go | 12 +- .../csi/node_helpers/plain/plugin_test.go | 6 +- frontend/csi/node_server.go | 45 +- frontend/csi/node_server_test.go | 16 +- frontend/csi/plugin.go | 32 +- frontend/csi/volume_publish_manager.go | 60 ++- frontend/csi/volume_publish_manager_test.go | 171 ++++---- main.go | 5 +- .../mock_filesystem/mock_filesystem_client.go | 162 +++++++ .../mock_json_utils.go | 8 +- .../mock_filesystem/mock_mount_client.go | 69 +++ .../mock_iscsi_filesystem_client.go | 66 --- storage_drivers/common.go | 3 +- storage_drivers/ontap/ontap_common.go | 3 +- storage_drivers/ontap/ontap_san.go | 2 +- storage_drivers/ontap/ontap_san_economy.go | 2 +- .../ontap/ontap_san_economy_test.go | 6 +- storage_drivers/ontap/ontap_san_nvme.go | 3 +- storage_drivers/ontap/ontap_san_nvme_test.go | 15 +- storage_drivers/ontap/ontap_san_test.go | 6 +- storage_drivers/solidfire/solidfire_san.go | 5 +- utils/adaptors.go | 14 - utils/{ => csiutils}/csiutils_windows.go | 2 +- utils/{ => csiutils}/csiutils_windows_test.go | 2 +- .../{ => csiutils}/csiutils_windows_types.go | 4 +- utils/devices_linux.go | 5 +- utils/fcp.go | 7 +- utils/fcp/fcp.go | 23 +- utils/{ => filesystem}/filesystem.go | 194 ++++----- utils/{ => filesystem}/filesystem_darwin.go | 16 +- .../filesystem_darwin_test.go | 24 +- utils/{ => filesystem}/filesystem_linux.go | 19 +- utils/filesystem/filesystem_linux_test.go | 188 +++++++++ utils/filesystem/filesystem_test.go | 395 ++++++++++++++++++ utils/{ => filesystem}/filesystem_windows.go | 28 +- .../filesystem_windows_test.go | 14 +- utils/filesystem/json.go | 95 +++++ utils/filesystem/utils.go | 27 ++ utils/filesystem_linux_test.go | 30 -- utils/filesystem_test.go | 337 --------------- utils/iscsi.go | 3 +- utils/iscsi/iscsi.go | 21 +- utils/iscsi/iscsi_test.go | 212 +++++----- utils/models/types.go | 12 +- utils/nvme.go | 9 +- utils/osutils_linux.go | 6 - utils/utils.go | 11 - utils/utils_test.go | 15 +- 52 files changed, 1476 insertions(+), 961 deletions(-) create mode 100644 mocks/mock_utils/mock_filesystem/mock_filesystem_client.go rename mocks/mock_utils/{mock_models => mock_filesystem}/mock_json_utils.go (87%) create mode 100644 mocks/mock_utils/mock_filesystem/mock_mount_client.go delete mode 100644 mocks/mock_utils/mock_iscsi/mock_iscsi_filesystem_client.go rename utils/{ => csiutils}/csiutils_windows.go (99%) rename utils/{ => csiutils}/csiutils_windows_test.go (94%) rename utils/{ => csiutils}/csiutils_windows_types.go (81%) rename utils/{ => filesystem}/filesystem.go (63%) rename utils/{ => filesystem}/filesystem_darwin.go (68%) rename utils/{ => filesystem}/filesystem_darwin_test.go (74%) rename utils/{ => filesystem}/filesystem_linux.go (87%) create mode 100644 utils/filesystem/filesystem_linux_test.go create mode 100644 utils/filesystem/filesystem_test.go rename utils/{ => filesystem}/filesystem_windows.go (58%) rename utils/{ => filesystem}/filesystem_windows_test.go (53%) create mode 100644 utils/filesystem/json.go create mode 100644 utils/filesystem/utils.go delete mode 100644 utils/filesystem_linux_test.go delete mode 100644 utils/filesystem_test.go diff --git a/config/config.go b/config/config.go index aab97d018..e02ea2066 100644 --- a/config/config.go +++ b/config/config.go @@ -96,12 +96,6 @@ const ( RawBlock VolumeMode = "Block" Filesystem VolumeMode = "Filesystem" - // Filesystem types - FsXfs = "xfs" - FsExt3 = "ext3" - FsExt4 = "ext4" - FsRaw = "raw" - /* Volume type constants */ OntapNFS VolumeType = "ONTAP_NFS" OntapISCSI VolumeType = "ONTAP_iSCSI" diff --git a/core/orchestrator_core.go b/core/orchestrator_core.go index 692ed0d82..97e126ad1 100644 --- a/core/orchestrator_core.go +++ b/core/orchestrator_core.go @@ -32,6 +32,7 @@ import ( "github.com/netapp/trident/utils" "github.com/netapp/trident/utils/errors" "github.com/netapp/trident/utils/fcp" + "github.com/netapp/trident/utils/filesystem" "github.com/netapp/trident/utils/iscsi" "github.com/netapp/trident/utils/models" "github.com/netapp/trident/utils/mount" @@ -101,6 +102,7 @@ type TridentOrchestrator struct { stopReconcileBackendLoop chan bool uuid string iscsi iscsi.ISCSI + fs filesystem.Filesystem fcp fcp.FCP mount mount.Mount } @@ -109,7 +111,7 @@ type TridentOrchestrator struct { func NewTridentOrchestrator(client persistentstore.Client) (*TridentOrchestrator, error) { // TODO (vivintw) the adaptors are being plugged in here as a temporary measure to prevent cyclic dependencies. // NewClient() must plugin default implementation of the various package clients. - iscsiClient, err := iscsi.New(utils.NewOSClient(), utils.NewDevicesClient(), utils.NewFilesystemClient()) + iscsiClient, err := iscsi.New(utils.NewOSClient(), utils.NewDevicesClient()) if err != nil { return nil, err } @@ -119,7 +121,7 @@ func NewTridentOrchestrator(client persistentstore.Client) (*TridentOrchestrator return nil, err } - fcpClent, err := fcp.New(utils.NewOSClient(), utils.NewDevicesClient(), utils.NewFilesystemClient()) + fcpClent, err := fcp.New(utils.NewOSClient(), utils.NewDevicesClient(), filesystem.New(mountClient)) if err != nil { return nil, err } @@ -140,6 +142,7 @@ func NewTridentOrchestrator(client persistentstore.Client) (*TridentOrchestrator iscsi: iscsiClient, fcp: fcpClent, mount: mountClient, + fs: filesystem.New(mountClient), }, nil } @@ -2670,7 +2673,7 @@ func (o *TridentOrchestrator) validateImportVolume(ctx context.Context, volumeCo // Make sure that for the Raw-block volume import we do not have ext3, ext4 or xfs filesystem specified if volumeConfig.VolumeMode == config.RawBlock { - if volumeConfig.FileSystem != "" && volumeConfig.FileSystem != config.FsRaw { + if volumeConfig.FileSystem != "" && volumeConfig.FileSystem != filesystem.Raw { return fmt.Errorf("cannot create raw-block volume %s with the filesystem %s", originalName, volumeConfig.FileSystem) } @@ -3621,7 +3624,7 @@ func (o *TridentOrchestrator) AttachVolume( } // Check if volume is already mounted - dfOutput, dfOuputErr := utils.GetDFOutput(ctx) + dfOutput, dfOuputErr := o.fs.GetDFOutput(ctx) if dfOuputErr != nil { err = fmt.Errorf("error checking if %v is already mounted: %v", mountpoint, dfOuputErr) return err diff --git a/frontend/csi/controller_server.go b/frontend/csi/controller_server.go index 7b8d1603d..932212f1a 100644 --- a/frontend/csi/controller_server.go +++ b/frontend/csi/controller_server.go @@ -24,6 +24,7 @@ import ( sa "github.com/netapp/trident/storage_attribute" "github.com/netapp/trident/utils" "github.com/netapp/trident/utils/errors" + "github.com/netapp/trident/utils/filesystem" "github.com/netapp/trident/utils/models" ) @@ -161,7 +162,7 @@ func (p *Plugin) CreateVolume( "raw block volumes are not supported for this container orchestrator") } volumeMode = tridentconfig.RawBlock - fsType = tridentconfig.FsRaw + fsType = filesystem.Raw } for _, csiAccessMode := range csiAccessModes { diff --git a/frontend/csi/node_helpers/kubernetes/plugin.go b/frontend/csi/node_helpers/kubernetes/plugin.go index d8b83fc49..2bfc9d5f1 100644 --- a/frontend/csi/node_helpers/kubernetes/plugin.go +++ b/frontend/csi/node_helpers/kubernetes/plugin.go @@ -53,6 +53,11 @@ func NewHelper(orchestrator core.Orchestrator, kubeConfigPath string, enableForc return nil, fmt.Errorf("could not initialize mount client; %v", err) } + publishManager, err := csi.NewVolumePublishManager() + if err != nil { + return nil, fmt.Errorf("could not initialize VolumePublishManager; %v", err) + } + h := &helper{ orchestrator: orchestrator, podsPath: kubeConfigPath + "/pods", @@ -60,7 +65,7 @@ func NewHelper(orchestrator core.Orchestrator, kubeConfigPath string, enableForc publishedPaths: make(map[string]map[string]struct{}), enableForceDetach: enableForceDetach, mount: mountClient, - VolumePublishManager: csi.NewVolumePublishManager(config.VolumeTrackingInfoPath), + VolumePublishManager: publishManager, } return h, nil diff --git a/frontend/csi/node_helpers/plain/plugin.go b/frontend/csi/node_helpers/plain/plugin.go index 2d218e1d2..13ed478f3 100644 --- a/frontend/csi/node_helpers/plain/plugin.go +++ b/frontend/csi/node_helpers/plain/plugin.go @@ -4,6 +4,7 @@ package plain import ( "context" + "fmt" "github.com/netapp/trident/config" "github.com/netapp/trident/core" @@ -19,17 +20,20 @@ type helper struct { } // NewHelper instantiates this plugin. -func NewHelper(orchestrator core.Orchestrator) frontend.Plugin { +func NewHelper(orchestrator core.Orchestrator) (frontend.Plugin, error) { ctx := GenerateRequestContext(nil, "", ContextSourceInternal, WorkflowPluginCreate, LogLayerCSIFrontend) Logc(ctx).Info("Initializing plain CSI helper frontend.") - volPubManager := csi.NewVolumePublishManager("") + publishManager, err := csi.NewVolumePublishManager() + if err != nil { + return nil, fmt.Errorf("could not initialize VolumePublishManager; %v", err) + } return &helper{ orchestrator: orchestrator, - VolumePublishManager: volPubManager, - } + VolumePublishManager: publishManager, + }, nil } // Activate starts this Trident frontend. diff --git a/frontend/csi/node_helpers/plain/plugin_test.go b/frontend/csi/node_helpers/plain/plugin_test.go index ca05b3be6..614c99f2f 100644 --- a/frontend/csi/node_helpers/plain/plugin_test.go +++ b/frontend/csi/node_helpers/plain/plugin_test.go @@ -201,14 +201,14 @@ func TestRemovePublishedPath_Succeeds(t *testing.T) { func TestNewHelper(t *testing.T) { mockCtrl := gomock.NewController(t) orchestrator := mockOrchestrator.NewMockOrchestrator(mockCtrl) - h := NewHelper(orchestrator) + h, _ := NewHelper(orchestrator) assert.NotNilf(t, h, "expected helper to not be nil") } func TestActivate(t *testing.T) { mockCtrl := gomock.NewController(t) orchestrator := mockOrchestrator.NewMockOrchestrator(mockCtrl) - h := NewHelper(orchestrator) + h, _ := NewHelper(orchestrator) err := h.Activate() assert.NoError(t, err) } @@ -216,6 +216,6 @@ func TestActivate(t *testing.T) { func TestVersion(t *testing.T) { mockCtrl := gomock.NewController(t) orchestrator := mockOrchestrator.NewMockOrchestrator(mockCtrl) - h := NewHelper(orchestrator) + h, _ := NewHelper(orchestrator) assert.Equal(t, csi.Version, h.Version()) } diff --git a/frontend/csi/node_server.go b/frontend/csi/node_server.go index 571ea173e..fbc50c2f7 100644 --- a/frontend/csi/node_server.go +++ b/frontend/csi/node_server.go @@ -27,6 +27,7 @@ import ( "github.com/netapp/trident/utils" "github.com/netapp/trident/utils/errors" "github.com/netapp/trident/utils/fcp" + "github.com/netapp/trident/utils/filesystem" "github.com/netapp/trident/utils/iscsi" "github.com/netapp/trident/utils/models" ) @@ -355,14 +356,14 @@ func (p *Plugin) NodeGetVolumeStats( } publishInfo := &trackingInfo.VolumePublishInfo - isRawBlock = publishInfo.FilesystemType == tridentconfig.FsRaw + isRawBlock = publishInfo.FilesystemType == filesystem.Raw } if isRawBlock { // Return no capacity info for raw block volumes, we cannot reliably determine the capacity. return &csi.NodeGetVolumeStatsResponse{}, nil } else { // If filesystem, return usage reported by FS. - available, capacity, usage, inodes, inodesFree, inodesUsed, err := utils.GetFilesystemStats( + available, capacity, usage, inodes, inodesFree, inodesUsed, err := p.fs.GetFilesystemStats( ctx, req.GetVolumePath()) if err != nil { Logc(ctx).Errorf("unable to get filesystem stats at path: %s; %v", req.GetVolumePath(), err) @@ -470,7 +471,7 @@ func (p *Plugin) nodeExpandVolume( Logc(ctx).WithField("volumeId", volumeId).Info("Filesystem expansion check is not required.") return nil case tridentconfig.Block: - if fsType, err = utils.VerifyFilesystemSupport(publishInfo.FilesystemType); err != nil { + if fsType, err = filesystem.VerifyFilesystemSupport(publishInfo.FilesystemType); err != nil { break } // We don't need to rescan mount devices for NVMe protocol backend. Automatic namespace rescanning happens @@ -523,8 +524,8 @@ func (p *Plugin) nodeExpandVolume( } // Expand filesystem. - if fsType != tridentconfig.FsRaw { - filesystemSize, err := utils.ExpandFilesystemOnNode(ctx, publishInfo, devicePath, stagingTargetPath, fsType, + if fsType != filesystem.Raw { + filesystemSize, err := p.fs.ExpandFilesystemOnNode(ctx, publishInfo, devicePath, stagingTargetPath, fsType, mountOptions) if err != nil { Logc(ctx).WithFields(LogFields{ @@ -983,7 +984,7 @@ func (p *Plugin) nodeUnstageSMBVolume( return &csi.NodeUnstageVolumeResponse{}, err } - mappingPath, err = utils.GetUnmountPath(ctx, trackingInfo) + mappingPath, err = p.fs.GetUnmountPath(ctx, trackingInfo) if err != nil { return nil, err } @@ -1389,7 +1390,7 @@ func (p *Plugin) nodePublishFCPVolume( } } - if publishInfo.FilesystemType == tridentconfig.FsRaw { + if publishInfo.FilesystemType == filesystem.Raw { if len(publishInfo.MountOptions) > 0 { publishInfo.MountOptions = utils.AppendToStringList(publishInfo.MountOptions, "bind", ",") @@ -1628,7 +1629,7 @@ func (p *Plugin) nodeUnstageISCSIVolume( return nil } - deviceInfo, err := p.deviceClient.GetDeviceInfoForLUN(ctx, hostSessionMap, int(publishInfo.IscsiLunNumber), + deviceInfo, err := p.devices.GetDeviceInfoForLUN(ctx, hostSessionMap, int(publishInfo.IscsiLunNumber), publishInfo.IscsiTargetIQN, false) if err != nil { Logc(ctx).WithError(err).Debug("Could not find devices.") @@ -1650,7 +1651,7 @@ func (p *Plugin) nodeUnstageISCSIVolume( "multipathDevice": deviceInfo.MultipathDevice, } - luksMapperPath, err = p.deviceClient.GetLUKSDeviceForMultipathDevice(deviceInfo.MultipathDevice) + luksMapperPath, err = p.devices.GetLUKSDeviceForMultipathDevice(deviceInfo.MultipathDevice) if err != nil { if !errors.IsNotFoundError(err) { Logc(ctx).WithFields(fields).WithError(err).Error("Failed to get LUKS device path from multipath device.") @@ -1662,7 +1663,7 @@ func (p *Plugin) nodeUnstageISCSIVolume( // Ensure the LUKS device is closed if the luksMapperPath is set. if luksMapperPath != "" { fields["luksDevice"] = luksMapperPath - err = p.deviceClient.EnsureLUKSDeviceClosedWithMaxWaitLimit(ctx, luksMapperPath) + err = p.devices.EnsureLUKSDeviceClosedWithMaxWaitLimit(ctx, luksMapperPath) if err != nil { if !errors.IsMaxWaitExceededError(err) { Logc(ctx).WithFields(fields).WithError(err).Error("Failed to close LUKS device.") @@ -1679,11 +1680,11 @@ func (p *Plugin) nodeUnstageISCSIVolume( } // Delete the device from the host. - unmappedMpathDevice, err := p.deviceClient.PrepareDeviceForRemoval(ctx, deviceInfo, publishInfo, nil, p.unsafeDetach, force) + unmappedMpathDevice, err := p.devices.PrepareDeviceForRemoval(ctx, deviceInfo, publishInfo, nil, p.unsafeDetach, force) if err != nil { if errors.IsISCSISameLunNumberError(err) { // There is a need to pass all the publish infos this time - unmappedMpathDevice, err = p.deviceClient.PrepareDeviceForRemoval(ctx, deviceInfo, publishInfo, + unmappedMpathDevice, err = p.devices.PrepareDeviceForRemoval(ctx, deviceInfo, publishInfo, p.readAllTrackingFiles(ctx), p.unsafeDetach, force) } @@ -1756,7 +1757,7 @@ func (p *Plugin) nodeUnstageISCSIVolume( // It needs to be removed prior to removing the 'unmappedMpathDevice' device below. if luksMapperPath != "" { // EnsureLUKSDeviceClosed will not return an error if the device is already closed or removed. - if err = p.deviceClient.EnsureLUKSDeviceClosed(ctx, luksMapperPath); err != nil { + if err = p.devices.EnsureLUKSDeviceClosed(ctx, luksMapperPath); err != nil { Logc(ctx).WithFields(LogFields{ "devicePath": luksMapperPath, }).WithError(err).Warning("Unable to remove LUKS mapper device.") @@ -1766,7 +1767,7 @@ func (p *Plugin) nodeUnstageISCSIVolume( } // If there is multipath device, flush(remove) mappings - if err := p.deviceClient.RemoveMultipathDeviceMapping(ctx, unmappedMpathDevice); err != nil { + if err := p.devices.RemoveMultipathDeviceMapping(ctx, unmappedMpathDevice); err != nil { return err } @@ -1832,7 +1833,7 @@ func (p *Plugin) nodePublishISCSIVolume( var err error if utils.IsLegacyLUKSDevicePath(devicePath) { // Supports legacy volumes that store the LUKS device path - luksDevice, err = p.deviceClient.NewLUKSDeviceFromMappingPath(ctx, devicePath, + luksDevice, err = p.devices.NewLUKSDeviceFromMappingPath(ctx, devicePath, req.VolumeContext["internalName"]) } else { luksDevice, err = utils.NewLUKSDevice(publishInfo.DevicePath, req.VolumeContext["internalName"]) @@ -1848,7 +1849,7 @@ func (p *Plugin) nodePublishISCSIVolume( // Mount LUKS device instead of mpath. devicePath = luksDevice.MappedDevicePath() } - isRawBlock := publishInfo.FilesystemType == tridentconfig.FsRaw + isRawBlock := publishInfo.FilesystemType == filesystem.Raw if isRawBlock { if len(publishInfo.MountOptions) > 0 { @@ -2179,7 +2180,7 @@ func (p *Plugin) selfHealingRectifySession(ctx context.Context, portal string, a case models.LoginScan: // Set FilesystemType to "raw" so that we only heal the session connectivity and not perform the mount and // filesystem related operations. - publishInfo.FilesystemType = tridentconfig.FsRaw + publishInfo.FilesystemType = filesystem.Raw volumeID, err := publishedISCSISessions.VolumeIDForPortalAndLUN(portal, lunID) if err != nil { @@ -2459,7 +2460,7 @@ func (p *Plugin) nodeStageNVMeVolume( } if isLUKS { - luksDevice, err := p.deviceClient.NewLUKSDeviceFromMappingPath(ctx, publishInfo.DevicePath, + luksDevice, err := p.devices.NewLUKSDeviceFromMappingPath(ctx, publishInfo.DevicePath, req.VolumeContext["internalName"]) if err != nil { return err @@ -2609,7 +2610,7 @@ func (p *Plugin) nodePublishNVMeVolume( if utils.ParseBool(publishInfo.LUKSEncryption) { // Rotate the LUKS passphrase if needed, on failure, log and continue to publish - luksDevice, err := p.deviceClient.NewLUKSDeviceFromMappingPath(ctx, publishInfo.DevicePath, + luksDevice, err := p.devices.NewLUKSDeviceFromMappingPath(ctx, publishInfo.DevicePath, req.VolumeContext["internalName"]) if err != nil { return nil, status.Error(codes.Internal, err.Error()) @@ -2620,7 +2621,7 @@ func (p *Plugin) nodePublishNVMeVolume( } } - isRawBlock := publishInfo.FilesystemType == tridentconfig.FsRaw + isRawBlock := publishInfo.FilesystemType == filesystem.Raw if isRawBlock { if len(publishInfo.MountOptions) > 0 { @@ -2667,11 +2668,11 @@ func (p *Plugin) nodeStageSANVolume( fsType = mountCapability.GetFsType() } - if fsType == tridentconfig.FsRaw && mountCapability != nil { + if fsType == filesystem.Raw && mountCapability != nil { return nil, status.Error(codes.InvalidArgument, "mount capability requested with raw blocks") } - if fsType != tridentconfig.FsRaw && blockCapability != nil { + if fsType != filesystem.Raw && blockCapability != nil { return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("block capability requested with %s", fsType)) } diff --git a/frontend/csi/node_server_test.go b/frontend/csi/node_server_test.go index 74cd9f7c2..cbbd6025d 100644 --- a/frontend/csi/node_server_test.go +++ b/frontend/csi/node_server_test.go @@ -15,7 +15,6 @@ import ( "github.com/stretchr/testify/assert" "go.uber.org/mock/gomock" - tridentconfig "github.com/netapp/trident/config" controllerAPI "github.com/netapp/trident/frontend/csi/controller_api" nodehelpers "github.com/netapp/trident/frontend/csi/node_helpers" mockcore "github.com/netapp/trident/mocks/mock_core" @@ -28,6 +27,7 @@ import ( "github.com/netapp/trident/utils" "github.com/netapp/trident/utils/crypto" "github.com/netapp/trident/utils/errors" + "github.com/netapp/trident/utils/filesystem" "github.com/netapp/trident/utils/iscsi" "github.com/netapp/trident/utils/models" "github.com/netapp/trident/utils/mount" @@ -68,10 +68,13 @@ func TestNodeStageVolume(t *testing.T) { }, } + mountClient, _ := mount.New() + for name, params := range tests { t.Run(name, func(t *testing.T) { plugin := &Plugin{ role: CSINode, + fs: filesystem.New(mountClient), } if params.getISCSIClient != nil { @@ -102,7 +105,7 @@ func TestNodeStageSANVolume(t *testing.T) { } noCapabilitiesRequest := NewNodeStageVolumeRequestBuilder().WithVolumeCapability(&csi.VolumeCapability{}).Build() - fileSystemRawMountCapabilityRequest := NewNodeStageVolumeRequestBuilder().WithFileSystemType(tridentconfig.FsRaw).Build() + fileSystemRawMountCapabilityRequest := NewNodeStageVolumeRequestBuilder().WithFileSystemType(filesystem.Raw).Build() fileSystemExt4BlockCapabilityRequest := NewNodeStageVolumeRequestBuilder().WithVolumeCapability( &csi.VolumeCapability{ AccessType: &csi.VolumeCapability_Block{ @@ -558,11 +561,14 @@ func TestNodeStageISCSIVolume(t *testing.T) { }, } + mountClient, _ := mount.New() + for name, params := range tests { t.Run(name, func(t *testing.T) { plugin := &Plugin{ role: CSINode, aesKey: params.aesKey, + fs: filesystem.New(mountClient), } if params.getISCSIClient != nil { @@ -1017,7 +1023,7 @@ func TestFixISCSISessions(t *testing.T) { }, } - iscsiClient, err := iscsi.New(utils.NewOSClient(), utils.NewDevicesClient(), utils.NewFilesystemClient()) + iscsiClient, err := iscsi.New(utils.NewOSClient(), utils.NewDevicesClient()) assert.NoError(t, err) nodeServer := &Plugin{ @@ -2444,7 +2450,7 @@ func TestNodeUnstageISCSIVolume(t *testing.T) { } if params.getDeviceClient != nil { - plugin.deviceClient = params.getDeviceClient() + plugin.devices = params.getDeviceClient() } if params.getMountClient != nil { @@ -2469,7 +2475,7 @@ func NewNodeStageVolumeRequestBuilder() *NodeStageVolumeRequestBuilder { PublishContext: map[string]string{ "protocol": "block", "sharedTarget": "false", - "filesystemType": tridentconfig.FsExt4, + "filesystemType": filesystem.Ext4, "useCHAP": "false", "iscsiLunNumber": "0", "iscsiTargetIqn": "iqn.2016-04.com.mock-iscsi:8a1e4b296331", diff --git a/frontend/csi/plugin.go b/frontend/csi/plugin.go index 3a68a4256..ad8687a01 100644 --- a/frontend/csi/plugin.go +++ b/frontend/csi/plugin.go @@ -24,6 +24,7 @@ import ( "github.com/netapp/trident/utils" "github.com/netapp/trident/utils/errors" "github.com/netapp/trident/utils/fcp" + "github.com/netapp/trident/utils/filesystem" "github.com/netapp/trident/utils/iscsi" "github.com/netapp/trident/utils/models" "github.com/netapp/trident/utils/mount" @@ -81,10 +82,11 @@ type Plugin struct { nvmeSelfHealingChannel chan struct{} nvmeSelfHealingInterval time.Duration - iscsi iscsi.ISCSI - deviceClient iscsi.Devices // TODO: this interface will be replaced once the device package is created - mount mount.Mount - fcp fcp.FCP + iscsi iscsi.ISCSI + devices iscsi.Devices // TODO: this interface will be replaced once the device package is created + mount mount.Mount + fs filesystem.Filesystem + fcp fcp.FCP } func NewControllerPlugin( @@ -159,7 +161,7 @@ func NewNodePlugin( // TODO (vivintw) the adaptors are being plugged in here as a temporary measure to prevent cyclic dependencies. // NewClient() must plugin default implementation of the various package clients. - iscsiClient, err := iscsi.New(utils.NewOSClient(), utils.NewDevicesClient(), utils.NewFilesystemClient()) + iscsiClient, err := iscsi.New(utils.NewOSClient(), utils.NewDevicesClient()) if err != nil { return nil, err } @@ -169,7 +171,9 @@ func NewNodePlugin( return nil, err } - fcpClient, err := fcp.New(utils.NewOSClient(), utils.NewDevicesClient(), utils.NewFilesystemClient()) + fs := filesystem.New(mountClient) + + fcpClient, err := fcp.New(utils.NewOSClient(), utils.NewDevicesClient(), fs) if err != nil { return nil, err } @@ -191,9 +195,10 @@ func NewNodePlugin( nvmeSelfHealingInterval: nvmeSelfHealingInterval, iscsi: iscsiClient, // NewClient() must plugin default implementation of the various package clients. - fcp: fcpClient, - deviceClient: utils.NewDevicesClient(), - mount: mountClient, + fcp: fcpClient, + devices: utils.NewDevicesClient(), + mount: mountClient, + fs: fs, } if runtime.GOOS == "windows" { @@ -264,7 +269,7 @@ func NewAllInOnePlugin( // TODO (vivintw) the adaptors are being plugged in here as a temporary measure to prevent cyclic dependencies. // NewClient() must plugin default implementation of the various package clients. - iscsiClient, err := iscsi.New(utils.NewOSClient(), utils.NewDevicesClient(), utils.NewFilesystemClient()) + iscsiClient, err := iscsi.New(utils.NewOSClient(), utils.NewDevicesClient()) if err != nil { return nil, err } @@ -274,7 +279,9 @@ func NewAllInOnePlugin( return nil, err } - fcpClient, err := fcp.New(utils.NewOSClient(), utils.NewDevicesClient(), utils.NewFilesystemClient()) + fs := filesystem.New(mountClient) + + fcpClient, err := fcp.New(utils.NewOSClient(), utils.NewDevicesClient(), fs) if err != nil { return nil, err } @@ -296,8 +303,9 @@ func NewAllInOnePlugin( nvmeSelfHealingInterval: nvmeSelfHealingInterval, iscsi: iscsiClient, fcp: fcpClient, - deviceClient: utils.NewDevicesClient(), + devices: utils.NewDevicesClient(), mount: mountClient, + fs: fs, } // Define controller capabilities diff --git a/frontend/csi/volume_publish_manager.go b/frontend/csi/volume_publish_manager.go index 75885af2e..67d1df31d 100644 --- a/frontend/csi/volume_publish_manager.go +++ b/frontend/csi/volume_publish_manager.go @@ -18,14 +18,15 @@ import ( . "github.com/netapp/trident/logging" "github.com/netapp/trident/utils" "github.com/netapp/trident/utils/errors" + "github.com/netapp/trident/utils/filesystem" "github.com/netapp/trident/utils/models" + "github.com/netapp/trident/utils/mount" ) const volumePublishInfoFilename = "volumePublishInfo.json" var ( - osFs = afero.NewOsFs() - fileDeleter = utils.DeleteFile + jsonRW = filesystem.NewJSONReaderWriter(afero.NewOsFs()) beforeUpdateTrackingFile = fiji.Register("beforeUpdateTrackingFile", "volume_publish_manager") beforeTrackingInfoFound = fiji.Register("beforeTrackingInfoFound", "volume_publish_manager") @@ -35,22 +36,37 @@ var ( type VolumePublishManager struct { volumeTrackingInfoPath string + fs filesystem.Filesystem + osFs afero.Fs } -type fileDeleterType func(context.Context, string, string) (string, error) +func NewVolumePublishManager() (*VolumePublishManager, error) { + infoPath := config.VolumeTrackingInfoPath + mountClient, err := mount.New() + if err != nil { + return nil, err + } + fsClient := filesystem.New(mountClient) + osFs := afero.NewOsFs() + return NewVolumePublishManagerDetailed(infoPath, fsClient, osFs), nil +} -func NewVolumePublishManager( +func NewVolumePublishManagerDetailed( volumeTrackingInfoPath string, + fs filesystem.Filesystem, + osFs afero.Fs, ) *VolumePublishManager { volManager := &VolumePublishManager{ volumeTrackingInfoPath: volumeTrackingInfoPath, + fs: fs, + osFs: osFs, } return volManager } // GetVolumeTrackingFiles returns all the tracking files in the tracking directory. func (v *VolumePublishManager) GetVolumeTrackingFiles() ([]os.FileInfo, error) { - return afero.ReadDir(osFs, v.volumeTrackingInfoPath) + return afero.ReadDir(v.osFs, v.volumeTrackingInfoPath) } // WriteTrackingInfo writes the serialized staging target path, publish info and publish paths for a volume to @@ -79,7 +95,7 @@ func (v *VolumePublishManager) WriteTrackingInfo( return err } - err := utils.JsonReaderWriter.WriteJSONFile(ctx, trackingInfo, tmpFile, "volume tracking info") + err := jsonRW.WriteJSONFile(ctx, trackingInfo, tmpFile, "volume tracking info") if err != nil { return err } @@ -92,7 +108,7 @@ func (v *VolumePublishManager) WriteTrackingInfo( return err } - return osFs.Rename(tmpFile, trackingFile) + return v.osFs.Rename(tmpFile, trackingFile) } // ReadTrackingInfo reads from Trident's own volume tracking location (/var/lib/trident/tracking) @@ -117,7 +133,7 @@ func (v *VolumePublishManager) readTrackingInfo( return nil, err } - err := utils.JsonReaderWriter.ReadJSONFile(ctx, &volumeTrackingInfo, path.Join(v.volumeTrackingInfoPath, filename), + err := jsonRW.ReadJSONFile(ctx, &volumeTrackingInfo, path.Join(v.volumeTrackingInfoPath, filename), "volume tracking info") if err != nil { return nil, err @@ -175,7 +191,7 @@ func (v *VolumePublishManager) DeleteTrackingInfo(ctx context.Context, volumeID Logc(ctx).WithFields(fields).Trace(">>>> DeleteTrackingInfo") defer Logc(ctx).WithFields(fields).Trace("<<<< DeleteTrackingInfo") - filename, err := fileDeleter(ctx, path.Join(v.volumeTrackingInfoPath, volumeID+".json"), "tracking") + filename, err := v.fs.DeleteFile(ctx, path.Join(v.volumeTrackingInfoPath, volumeID+".json"), "tracking") if err != nil { return err } @@ -201,7 +217,7 @@ func (v *VolumePublishManager) ensureTrackingFileCorrect( // In 23.01 and 23.04, the upgrade logic did not consider Raw devices when // populating published paths, thus there is a need to verify if the correct // published paths are present or not. - if volumeTrackingInfo.VolumePublishInfo.FilesystemType == config.FsRaw { + if volumeTrackingInfo.VolumePublishInfo.FilesystemType == filesystem.Raw { Logc(ctx).Debug("Ensuring raw device have published paths.") if len(volumeTrackingInfo.PublishedPaths) == 0 && len(publishedPaths) != 0 { @@ -309,7 +325,7 @@ func (v *VolumePublishManager) UpgradeVolumeTrackingFile( errorTemplate := "error upgrading the volume tracking file for volume: %s :%v" file := path.Join(v.volumeTrackingInfoPath, volumeId+".json") - err = utils.JsonReaderWriter.ReadJSONFile(ctx, volumeTrackingInfo, file, "volume tracking info") + err = jsonRW.ReadJSONFile(ctx, volumeTrackingInfo, file, "volume tracking info") if err != nil { if !isFileValidJSON(err) { return true, nil @@ -332,11 +348,11 @@ func (v *VolumePublishManager) UpgradeVolumeTrackingFile( } file = path.Join(volumeTrackingInfo.StagingTargetPath, volumePublishInfoFilename) - err = utils.JsonReaderWriter.ReadJSONFile(ctx, publishInfo, file, "publish info") + err = jsonRW.ReadJSONFile(ctx, publishInfo, file, "publish info") if err != nil { if !isFileValidJSON(err) { // If the staged device info file is not valid, it will never be useful again, regardless of retries. - deleteStagedDeviceInfo(ctx, volumeTrackingInfo.StagingTargetPath, volumeId) + v.deleteStagedDeviceInfo(ctx, volumeTrackingInfo.StagingTargetPath, volumeId) return true, nil } return false, TerminalReconciliationError(fmt.Sprintf(errorTemplate, volumeId, err)) @@ -346,7 +362,7 @@ func (v *VolumePublishManager) UpgradeVolumeTrackingFile( if err != nil { // If we cannot determine the volume protocol from the staged device info, then there is no reason to keep // it around. - deleteStagedDeviceInfo(ctx, volumeTrackingInfo.StagingTargetPath, volumeId) + v.deleteStagedDeviceInfo(ctx, volumeTrackingInfo.StagingTargetPath, volumeId) return true, nil } @@ -366,7 +382,7 @@ func (v *VolumePublishManager) UpgradeVolumeTrackingFile( } // Remove the old file in the staging path now that its contents have been moved to the new tracking file. - _ = clearStagedDeviceInfo(ctx, volumeTrackingInfo.StagingTargetPath, volumeId) + _ = v.clearStagedDeviceInfo(ctx, volumeTrackingInfo.StagingTargetPath, volumeId) Logc(ctx).Debug("Volume tracking method upgraded.") @@ -381,7 +397,7 @@ func (v *VolumePublishManager) ValidateTrackingFile(ctx context.Context, volumeI defer Logc(ctx).WithFields(fields).Trace("<<<< ValidateTrackingFile") filename := path.Join(v.volumeTrackingInfoPath, volumeId+".json") - err := utils.JsonReaderWriter.ReadJSONFile(ctx, &trackingInfo, filename, "volume tracking") + err := jsonRW.ReadJSONFile(ctx, &trackingInfo, filename, "volume tracking") if err != nil { if !isFileValidJSON(err) { return true, nil @@ -399,7 +415,7 @@ func (v *VolumePublishManager) ValidateTrackingFile(ctx context.Context, volumeI return false, nil } - _, err = osFs.Stat(stagePath) + _, err = v.osFs.Stat(stagePath) if err != nil { // If the stat failed for any reason other than it not existing, we need to return a failed validation error so // that the node plugin will be restarted, and validation can be retried. @@ -432,7 +448,7 @@ func (v *VolumePublishManager) DeleteFailedUpgradeTrackingFile(ctx context.Conte filename := path.Join(config.VolumeTrackingInfoPath, file.Name()) if strings.Contains(file.Name(), "tmp") { - _, err := fileDeleter(ctx, filename, "tmp volume tracking file") + _, err := v.fs.DeleteFile(ctx, filename, "tmp volume tracking file") if err != nil { Logc(ctx).WithField("filename", filename).Warn("Could not delete temporary volume tracking file.") } @@ -441,14 +457,14 @@ func (v *VolumePublishManager) DeleteFailedUpgradeTrackingFile(ctx context.Conte // clearStagedDeviceInfo removes the volume info at the staging target path. This method is idempotent, // in that if the file doesn't exist, no error is generated. -func clearStagedDeviceInfo(ctx context.Context, stagingTargetPath, volumeId string) error { +func (v *VolumePublishManager) clearStagedDeviceInfo(ctx context.Context, stagingTargetPath, volumeId string) error { fields := LogFields{"stagingTargetPath": stagingTargetPath, "volumeId": volumeId} Logc(ctx).WithFields(fields).Trace(">>>> clearStagedDeviceInfo") defer Logc(ctx).WithFields(fields).Trace("<<<< clearStagedDeviceInfo") stagingFilename := path.Join(stagingTargetPath, volumePublishInfoFilename) - if err := osFs.Remove(stagingFilename); err != nil { + if err := v.osFs.Remove(stagingFilename); err != nil { if os.IsNotExist(err) { Logc(ctx).WithFields(fields).Warning("Staging file does not exist.") return nil @@ -470,8 +486,8 @@ func isFileValidJSON(err error) bool { return true } -func deleteStagedDeviceInfo(ctx context.Context, stagingPath, volumeId string) { - err := clearStagedDeviceInfo(ctx, stagingPath, volumeId) +func (v *VolumePublishManager) deleteStagedDeviceInfo(ctx context.Context, stagingPath, volumeId string) { + err := v.clearStagedDeviceInfo(ctx, stagingPath, volumeId) if err != nil { fields := LogFields{"volumeId": volumeId, "stagingPath": stagingPath} Logc(ctx).WithFields(fields).Warning(fmt.Sprintf("Error deleting staged device info: %v", err)) diff --git a/frontend/csi/volume_publish_manager_test.go b/frontend/csi/volume_publish_manager_test.go index c8fa72caa..73f2fb674 100644 --- a/frontend/csi/volume_publish_manager_test.go +++ b/frontend/csi/volume_publish_manager_test.go @@ -15,24 +15,24 @@ import ( "go.uber.org/mock/gomock" "github.com/netapp/trident/config" + "github.com/netapp/trident/mocks/mock_utils/mock_filesystem" "github.com/netapp/trident/mocks/mock_utils/mock_iscsi" - "github.com/netapp/trident/mocks/mock_utils/mock_models" sa "github.com/netapp/trident/storage_attribute" - "github.com/netapp/trident/utils" "github.com/netapp/trident/utils/errors" + "github.com/netapp/trident/utils/exec" + "github.com/netapp/trident/utils/filesystem" "github.com/netapp/trident/utils/models" ) func TestNewVolumePublishManager(t *testing.T) { aPath := "foo" - v := NewVolumePublishManager(aPath) + v := NewVolumePublishManagerDetailed(aPath, filesystem.New(nil), afero.NewOsFs()) assert.Equal(t, aPath, v.volumeTrackingInfoPath, "volume publish manager did not contain expected path") } func TestGetVolumeTrackingFiles(t *testing.T) { - defer func() { osFs = afero.NewOsFs() }() - osFs = afero.NewMemMapFs() - v := NewVolumePublishManager(config.VolumeTrackingInfoPath) + osFs := afero.NewMemMapFs() + v := NewVolumePublishManagerDetailed(config.VolumeTrackingInfoPath, filesystem.New(nil), osFs) trackPath := config.VolumeTrackingInfoPath _, err := osFs.Create(path.Join(trackPath, "pvc-123")) @@ -54,13 +54,16 @@ func TestGetVolumeTrackingFiles(t *testing.T) { func TestWriteTrackingInfo(t *testing.T) { mockCtrl := gomock.NewController(t) - mockJSONUtils := mock_models.NewMockJSONReaderWriter(mockCtrl) + mockJSONUtils := mock_filesystem.NewMockJSONReaderWriter(mockCtrl) - defer func() { osFs = afero.NewOsFs() }() - osFs = afero.NewMemMapFs() - utils.JsonReaderWriter = mockJSONUtils + osFs := afero.NewMemMapFs() + + defer func(previousJsonRW filesystem.JSONReaderWriter) { + jsonRW = previousJsonRW + }(jsonRW) + jsonRW = mockJSONUtils - v := NewVolumePublishManager("") + v := NewVolumePublishManagerDetailed("", filesystem.New(nil), osFs) volId := "pvc-123" fName := volId + ".json" @@ -84,13 +87,15 @@ func TestWriteTrackingInfo(t *testing.T) { func TestReadTrackingInfo(t *testing.T) { mockCtrl := gomock.NewController(t) - mockJSONUtils := mock_models.NewMockJSONReaderWriter(mockCtrl) + mockJSONUtils := mock_filesystem.NewMockJSONReaderWriter(mockCtrl) - defer func() { osFs = afero.NewOsFs() }() - osFs = afero.NewMemMapFs() - utils.JsonReaderWriter = mockJSONUtils + defer func(previousJsonRW filesystem.JSONReaderWriter) { + jsonRW = previousJsonRW + }(jsonRW) + jsonRW = mockJSONUtils - v := NewVolumePublishManager("") + osFs := afero.NewMemMapFs() + v := NewVolumePublishManagerDetailed("", filesystem.New(nil), osFs) volId := "pvc-123" fName := volId + ".json" @@ -118,12 +123,10 @@ func TestReadTrackingInfo(t *testing.T) { } func TestListVolumeTrackingInfo_FailsToGetVolumeTrackingFiles(t *testing.T) { - defer func(original afero.Fs) { osFs = original }(osFs) - osFs = afero.NewMemMapFs() - // Set up the volume publish manager. trackPath := config.VolumeTrackingInfoPath - v := NewVolumePublishManager(trackPath) + osFs := afero.NewMemMapFs() + v := NewVolumePublishManagerDetailed(trackPath, filesystem.New(nil), osFs) // Without setting up the directory or files in memory, this will fail at GetVolumeTrackingFiles. allTrackingInfo, err := v.ListVolumeTrackingInfo(ctx) @@ -132,12 +135,10 @@ func TestListVolumeTrackingInfo_FailsToGetVolumeTrackingFiles(t *testing.T) { } func TestListVolumeTrackingInfo_FailsWhenNoTrackingFilesFound(t *testing.T) { - defer func(original afero.Fs) { osFs = original }(osFs) - osFs = afero.NewMemMapFs() - // Set up the volume tracking directory and publish manager. trackPath := config.VolumeTrackingInfoPath - v := NewVolumePublishManager(trackPath) + osFs := afero.NewMemMapFs() + v := NewVolumePublishManagerDetailed(trackPath, filesystem.New(nil), osFs) if err := osFs.Mkdir(trackPath, 0o600); err != nil { t.Fatalf("failed to create tracking file directory in memory fs for testing; %v", err) } @@ -150,16 +151,16 @@ func TestListVolumeTrackingInfo_FailsWhenNoTrackingFilesFound(t *testing.T) { func TestListVolumeTrackingInfo_FailsToReadTrackingInfo(t *testing.T) { mockCtrl := gomock.NewController(t) - defer func(original models.JSONReaderWriter) { utils.JsonReaderWriter = original }(utils.JsonReaderWriter) - jsonReaderWriter := mock_models.NewMockJSONReaderWriter(mockCtrl) - utils.JsonReaderWriter = jsonReaderWriter - - defer func(original afero.Fs) { osFs = original }(osFs) - osFs = afero.NewMemMapFs() + jsonReaderWriter := mock_filesystem.NewMockJSONReaderWriter(mockCtrl) + defer func(previousJsonRW filesystem.JSONReaderWriter) { + jsonRW = previousJsonRW + }(jsonRW) + jsonRW = jsonReaderWriter // Set up the volume tracking directory and files. trackPath := config.VolumeTrackingInfoPath - v := NewVolumePublishManager(trackPath) + osFs := afero.NewMemMapFs() + v := NewVolumePublishManagerDetailed(trackPath, filesystem.New(nil), osFs) volumeOne := "pvc-85987a99-648d-4d84-95df-47d0256ca2ab" volumeTrackingInfo := &models.VolumeTrackingInfo{ VolumePublishInfo: models.VolumePublishInfo{}, @@ -199,16 +200,14 @@ func TestListVolumeTrackingInfo_FailsToReadTrackingInfo(t *testing.T) { func TestListVolumeTrackingInfo_SucceedsToListTrackingFileInformation(t *testing.T) { mockCtrl := gomock.NewController(t) - defer func(original models.JSONReaderWriter) { utils.JsonReaderWriter = original }(utils.JsonReaderWriter) - jsonReaderWriter := mock_models.NewMockJSONReaderWriter(mockCtrl) - utils.JsonReaderWriter = jsonReaderWriter - - defer func(original afero.Fs) { osFs = original }(osFs) - osFs = afero.NewMemMapFs() + defer func(original filesystem.JSONReaderWriter) { jsonRW = original }(jsonRW) + jsonReaderWriter := mock_filesystem.NewMockJSONReaderWriter(mockCtrl) + jsonRW = jsonReaderWriter // Set up the volume tracking directory and files. trackPath := config.VolumeTrackingInfoPath - v := NewVolumePublishManager(trackPath) + osFs := afero.NewMemMapFs() + v := NewVolumePublishManagerDetailed(trackPath, filesystem.New(nil), osFs) volumeOne := "pvc-85987a99-648d-4d84-95df-47d0256ca2ab" volumeTrackingInfo := &models.VolumeTrackingInfo{ VolumePublishInfo: models.VolumePublishInfo{}, @@ -248,19 +247,15 @@ func TestListVolumeTrackingInfo_SucceedsToListTrackingFileInformation(t *testing func TestDeleteTrackingInfo(t *testing.T) { volName := "pvc-123" - oldDeleter := fileDeleter - defer func() { fileDeleter = oldDeleter }() - fileDeleter = func(ctx context.Context, filepath, fileDescription string) (string, error) { - return "", nil - } - v := NewVolumePublishManager("") + osFs := afero.NewMemMapFs() + mockFilesytesm := mock_filesystem.NewMockFilesystem(gomock.NewController(t)) + v := NewVolumePublishManagerDetailed("", mockFilesytesm, osFs) + mockFilesytesm.EXPECT().DeleteFile(gomock.Any(), gomock.Any(), gomock.Any()).Return("", nil) err := v.DeleteTrackingInfo(context.Background(), volName) assert.NoError(t, err, "expected no error deleting the tracking info") - fileDeleter = func(ctx context.Context, filepath, fileDescription string) (string, error) { - return "", errors.New("foo") - } + mockFilesytesm.EXPECT().DeleteFile(gomock.Any(), gomock.Any(), gomock.Any()).Return("", errors.New("foo")) err = v.DeleteTrackingInfo(context.Background(), volName) assert.Error(t, err, "expected error if delete tracking info fails") assert.Equal(t, "foo", err.Error(), "expected the error we threw") @@ -268,15 +263,17 @@ func TestDeleteTrackingInfo(t *testing.T) { func TestUpgradeVolumeTrackingFile(t *testing.T) { mockCtrl := gomock.NewController(t) - jsonReaderWriter := mock_models.NewMockJSONReaderWriter(mockCtrl) - original := utils.JsonReaderWriter - defer func() { utils.JsonReaderWriter = original }() - utils.JsonReaderWriter = jsonReaderWriter - defer func() { osFs = afero.NewOsFs() }() - osFs = afero.NewMemMapFs() + jsonReaderWriter := mock_filesystem.NewMockJSONReaderWriter(mockCtrl) + defer func(previousJsonRW filesystem.JSONReaderWriter) { + jsonRW = previousJsonRW + }(jsonRW) + jsonRW = jsonReaderWriter - stagePath := "/foo" + osFs := afero.NewMemMapFs() trackPath := "/bar" + v := NewVolumePublishManagerDetailed(trackPath, filesystem.New(nil), osFs) + + stagePath := "/foo" trackInfoAndPath := models.VolumeTrackingInfo{} trackInfoAndPath.StagingTargetPath = stagePath pubInfoNfsIp := models.VolumePublishInfo{} @@ -284,7 +281,6 @@ func TestUpgradeVolumeTrackingFile(t *testing.T) { volName := "pvc-123" fName := volName + ".json" pubPaths := map[string]struct{}{} - v := NewVolumePublishManager(trackPath) stagedDeviceInfo := path.Join(stagePath, volumePublishInfoFilename) trackingInfoFile := path.Join(trackPath, fName) tmpTrackingInfoFile := path.Join(trackPath, "tmp-"+fName) @@ -335,16 +331,18 @@ func TestUpgradeVolumeTrackingFile(t *testing.T) { func TestUpgradeVolumeTrackingFile_MissingDevicePathBeforeUpgrade(t *testing.T) { mockCtrl := gomock.NewController(t) - jsonReaderWriter := mock_models.NewMockJSONReaderWriter(mockCtrl) - original := utils.JsonReaderWriter - defer func() { utils.JsonReaderWriter = original }() - utils.JsonReaderWriter = jsonReaderWriter - defer func() { osFs = afero.NewOsFs() }() - osFs = afero.NewMemMapFs() + jsonReaderWriter := mock_filesystem.NewMockJSONReaderWriter(mockCtrl) + defer func(previousJsonRW filesystem.JSONReaderWriter) { + jsonRW = previousJsonRW + }(jsonRW) + jsonRW = jsonReaderWriter + osFs := afero.NewMemMapFs() stagePath := "/foo" trackPath := "/bar" + v := NewVolumePublishManagerDetailed(trackPath, filesystem.New(nil), osFs) + basePubInfo := models.VolumePublishInfo{} basePubInfo.NfsServerIP = "1.1.1.1" basePubInfo.FilesystemType = "somefs" @@ -355,7 +353,6 @@ func TestUpgradeVolumeTrackingFile_MissingDevicePathBeforeUpgrade(t *testing.T) volName := "pvc-123" fName := volName + ".json" pubPaths := map[string]struct{}{} - v := NewVolumePublishManager(trackPath) trackingInfoFile := path.Join(trackPath, fName) stagedDeviceInfo := path.Join(stagePath, volumePublishInfoFilename) tmpTrackingInfoFile := path.Join(trackPath, "tmp-"+fName) @@ -426,12 +423,12 @@ func TestUpgradeVolumeTrackingFile_MissingDevicePathBeforeUpgrade(t *testing.T) func TestUpgradeVolumeTrackingFile_MissingDevicePathAfterUpgrade(t *testing.T) { mockCtrl := gomock.NewController(t) - jsonReaderWriter := mock_models.NewMockJSONReaderWriter(mockCtrl) - original := utils.JsonReaderWriter - defer func() { utils.JsonReaderWriter = original }() - utils.JsonReaderWriter = jsonReaderWriter - defer func() { osFs = afero.NewOsFs() }() - osFs = afero.NewMemMapFs() + jsonReaderWriter := mock_filesystem.NewMockJSONReaderWriter(mockCtrl) + defer func(previousJsonRW filesystem.JSONReaderWriter) { + jsonRW = previousJsonRW + }(jsonRW) + jsonRW = jsonReaderWriter + osFs := afero.NewMemMapFs() stagePath := "/foo" trackPath := "/bar" @@ -456,7 +453,7 @@ func TestUpgradeVolumeTrackingFile_MissingDevicePathAfterUpgrade(t *testing.T) { volName := "pvc-123" fName := volName + ".json" pubPaths := map[string]struct{}{} - v := NewVolumePublishManager(trackPath) + v := NewVolumePublishManagerDetailed(trackPath, filesystem.New(nil), osFs) trackingInfoFile := path.Join(trackPath, fName) tmpTrackingInfoFile := path.Join(trackPath, "tmp-"+fName) @@ -575,25 +572,22 @@ func TestUpgradeVolumeTrackingFile_MissingDevicePathAfterUpgrade(t *testing.T) { func TestValidateTrackingFile(t *testing.T) { mockCtrl := gomock.NewController(t) mockiSCSIUtils := mock_iscsi.NewMockIscsiReconcileUtils(mockCtrl) - jsonReaderWriter := mock_models.NewMockJSONReaderWriter(mockCtrl) + jsonReaderWriter := mock_filesystem.NewMockJSONReaderWriter(mockCtrl) - defer func() { osFs = afero.NewOsFs() }() - osFs = afero.NewMemMapFs() - utils.JsonReaderWriter = jsonReaderWriter - oldDeleter := fileDeleter + osFs := afero.NewMemMapFs() + defer func(previousJsonRW filesystem.JSONReaderWriter) { + jsonRW = previousJsonRW + }(jsonRW) + jsonRW = jsonReaderWriter - defer func() { fileDeleter = oldDeleter }() - fileDeleter = func(ctx context.Context, filepath, fileDescription string) (string, error) { - return "", nil - } + trackPath := "." + v := NewVolumePublishManagerDetailed(trackPath, filesystem.New(nil), osFs) oldiSCSIUtils := iscsiUtils defer func() { iscsiUtils = oldiSCSIUtils }() iscsiUtils = mockiSCSIUtils stagePath := "/foo" - trackPath := "." - v := NewVolumePublishManager(trackPath) volName := "pvc-123" fName := volName + ".json" fsType := "ext4" @@ -664,13 +658,11 @@ func TestValidateTrackingFile(t *testing.T) { } func TestDeleteFailedUpgradeTrackingFile(t *testing.T) { - filename := "tmp-foo" - v := NewVolumePublishManager(config.VolumeTrackingInfoPath) + filename := "/var/lib/trident/tracking/tmp-foo" + osFs := afero.NewMemMapFs() - defer func() { osFs = afero.NewOsFs() }() - osFs = afero.NewMemMapFs() - defer func(fn fileDeleterType) { fileDeleter = fn }(fileDeleter) - fileDeleter = func(ctx context.Context, f, fDesc string) (string, error) { return "", osFs.Remove(filename) } + v := NewVolumePublishManagerDetailed(config.VolumeTrackingInfoPath, filesystem.NewDetailed(exec.NewCommand(), osFs, nil), + osFs) file, _ := osFs.Create(filename) fileinfo, _ := file.Stat() @@ -682,16 +674,17 @@ func TestDeleteFailedUpgradeTrackingFile(t *testing.T) { func TestClearStagedDeviceInfo(t *testing.T) { filename := volumePublishInfoFilename + osFs := afero.NewMemMapFs() + v := NewVolumePublishManagerDetailed(config.VolumeTrackingInfoPath, filesystem.New(nil), osFs) defer func() { osFs = afero.NewOsFs() }() - osFs = afero.NewMemMapFs() // happy path _, err := osFs.Create(filename) _, statErr := osFs.Stat(filename) assert.NoError(t, err, "expected no error creating a test file") assert.NoError(t, statErr, "expected to be able to stat just-created test file") - err = clearStagedDeviceInfo(context.Background(), ".", "pvc-123") + err = v.clearStagedDeviceInfo(context.Background(), ".", "pvc-123") _, statErr = osFs.Stat(filename) assert.NoError(t, err, "expected test file to exist before deletion") @@ -700,6 +693,6 @@ func TestClearStagedDeviceInfo(t *testing.T) { // does not exist case _, statErr = osFs.Stat(filename) assert.Error(t, statErr, "file should not exist") - err = clearStagedDeviceInfo(context.Background(), ".", "pvc-123") + err = v.clearStagedDeviceInfo(context.Background(), ".", "pvc-123") assert.NoError(t, err, "clear staged tracking file should not fail if the file doesn't exist") } diff --git a/main.go b/main.go index 5b4829bb4..e388ffc4b 100644 --- a/main.go +++ b/main.go @@ -432,7 +432,10 @@ func main() { } } else { hybridControllerFrontend = plainctrlhelper.NewHelper(orchestrator) - hybridNodeFrontend = plainnodehelper.NewHelper(orchestrator) + hybridNodeFrontend, err = plainnodehelper.NewHelper(orchestrator) + if err != nil { + Log().Fatalf("Unable to start the K8S hybrid controller frontend: '%s'", err) + } } if *csiRole == csi.CSIController || *csiRole == csi.CSIAllInOne { diff --git a/mocks/mock_utils/mock_filesystem/mock_filesystem_client.go b/mocks/mock_utils/mock_filesystem/mock_filesystem_client.go new file mode 100644 index 000000000..fcb126e8b --- /dev/null +++ b/mocks/mock_utils/mock_filesystem/mock_filesystem_client.go @@ -0,0 +1,162 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/netapp/trident/utils/filesystem (interfaces: Filesystem) +// +// Generated by this command: +// +// mockgen -destination=../../mocks/mock_utils/mock_filesystem/mock_filesystem_client.go github.com/netapp/trident/utils/filesystem Filesystem +// + +// Package mock_filesystem is a generated GoMock package. +package mock_filesystem + +import ( + context "context" + reflect "reflect" + + models "github.com/netapp/trident/utils/models" + gomock "go.uber.org/mock/gomock" +) + +// MockFilesystem is a mock of Filesystem interface. +type MockFilesystem struct { + ctrl *gomock.Controller + recorder *MockFilesystemMockRecorder +} + +// MockFilesystemMockRecorder is the mock recorder for MockFilesystem. +type MockFilesystemMockRecorder struct { + mock *MockFilesystem +} + +// NewMockFilesystem creates a new mock instance. +func NewMockFilesystem(ctrl *gomock.Controller) *MockFilesystem { + mock := &MockFilesystem{ctrl: ctrl} + mock.recorder = &MockFilesystemMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockFilesystem) EXPECT() *MockFilesystemMockRecorder { + return m.recorder +} + +// DeleteFile mocks base method. +func (m *MockFilesystem) DeleteFile(arg0 context.Context, arg1, arg2 string) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteFile", arg0, arg1, arg2) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DeleteFile indicates an expected call of DeleteFile. +func (mr *MockFilesystemMockRecorder) DeleteFile(arg0, arg1, arg2 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteFile", reflect.TypeOf((*MockFilesystem)(nil).DeleteFile), arg0, arg1, arg2) +} + +// ExpandFilesystemOnNode mocks base method. +func (m *MockFilesystem) ExpandFilesystemOnNode(arg0 context.Context, arg1 *models.VolumePublishInfo, arg2, arg3, arg4, arg5 string) (int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExpandFilesystemOnNode", arg0, arg1, arg2, arg3, arg4, arg5) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ExpandFilesystemOnNode indicates an expected call of ExpandFilesystemOnNode. +func (mr *MockFilesystemMockRecorder) ExpandFilesystemOnNode(arg0, arg1, arg2, arg3, arg4, arg5 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExpandFilesystemOnNode", reflect.TypeOf((*MockFilesystem)(nil).ExpandFilesystemOnNode), arg0, arg1, arg2, arg3, arg4, arg5) +} + +// FormatVolume mocks base method. +func (m *MockFilesystem) FormatVolume(arg0 context.Context, arg1, arg2, arg3 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FormatVolume", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(error) + return ret0 +} + +// FormatVolume indicates an expected call of FormatVolume. +func (mr *MockFilesystemMockRecorder) FormatVolume(arg0, arg1, arg2, arg3 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FormatVolume", reflect.TypeOf((*MockFilesystem)(nil).FormatVolume), arg0, arg1, arg2, arg3) +} + +// GenerateAnonymousMemFile mocks base method. +func (m *MockFilesystem) GenerateAnonymousMemFile(arg0, arg1 string) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GenerateAnonymousMemFile", arg0, arg1) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GenerateAnonymousMemFile indicates an expected call of GenerateAnonymousMemFile. +func (mr *MockFilesystemMockRecorder) GenerateAnonymousMemFile(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GenerateAnonymousMemFile", reflect.TypeOf((*MockFilesystem)(nil).GenerateAnonymousMemFile), arg0, arg1) +} + +// GetDFOutput mocks base method. +func (m *MockFilesystem) GetDFOutput(arg0 context.Context) ([]models.DFInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetDFOutput", arg0) + ret0, _ := ret[0].([]models.DFInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetDFOutput indicates an expected call of GetDFOutput. +func (mr *MockFilesystemMockRecorder) GetDFOutput(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDFOutput", reflect.TypeOf((*MockFilesystem)(nil).GetDFOutput), arg0) +} + +// GetFilesystemStats mocks base method. +func (m *MockFilesystem) GetFilesystemStats(arg0 context.Context, arg1 string) (int64, int64, int64, int64, int64, int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFilesystemStats", arg0, arg1) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(int64) + ret2, _ := ret[2].(int64) + ret3, _ := ret[3].(int64) + ret4, _ := ret[4].(int64) + ret5, _ := ret[5].(int64) + ret6, _ := ret[6].(error) + return ret0, ret1, ret2, ret3, ret4, ret5, ret6 +} + +// GetFilesystemStats indicates an expected call of GetFilesystemStats. +func (mr *MockFilesystemMockRecorder) GetFilesystemStats(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFilesystemStats", reflect.TypeOf((*MockFilesystem)(nil).GetFilesystemStats), arg0, arg1) +} + +// GetUnmountPath mocks base method. +func (m *MockFilesystem) GetUnmountPath(arg0 context.Context, arg1 *models.VolumeTrackingInfo) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUnmountPath", arg0, arg1) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUnmountPath indicates an expected call of GetUnmountPath. +func (mr *MockFilesystemMockRecorder) GetUnmountPath(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUnmountPath", reflect.TypeOf((*MockFilesystem)(nil).GetUnmountPath), arg0, arg1) +} + +// RepairVolume mocks base method. +func (m *MockFilesystem) RepairVolume(arg0 context.Context, arg1, arg2 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RepairVolume", arg0, arg1, arg2) +} + +// RepairVolume indicates an expected call of RepairVolume. +func (mr *MockFilesystemMockRecorder) RepairVolume(arg0, arg1, arg2 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RepairVolume", reflect.TypeOf((*MockFilesystem)(nil).RepairVolume), arg0, arg1, arg2) +} diff --git a/mocks/mock_utils/mock_models/mock_json_utils.go b/mocks/mock_utils/mock_filesystem/mock_json_utils.go similarity index 87% rename from mocks/mock_utils/mock_models/mock_json_utils.go rename to mocks/mock_utils/mock_filesystem/mock_json_utils.go index acf2ec65c..5fc8ff707 100644 --- a/mocks/mock_utils/mock_models/mock_json_utils.go +++ b/mocks/mock_utils/mock_filesystem/mock_json_utils.go @@ -1,13 +1,13 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/netapp/trident/utils/models (interfaces: JSONReaderWriter) +// Source: github.com/netapp/trident/utils/filesystem (interfaces: JSONReaderWriter) // // Generated by this command: // -// mockgen -destination=../../mocks/mock_utils/mock_models/mock_json_utils.go github.com/netapp/trident/utils/models JSONReaderWriter +// mockgen -destination=../../mocks/mock_utils/mock_filesystem/mock_json_utils.go github.com/netapp/trident/utils/filesystem JSONReaderWriter // -// Package mock_models is a generated GoMock package. -package mock_models +// Package mock_filesystem is a generated GoMock package. +package mock_filesystem import ( context "context" diff --git a/mocks/mock_utils/mock_filesystem/mock_mount_client.go b/mocks/mock_utils/mock_filesystem/mock_mount_client.go new file mode 100644 index 000000000..df6394ea2 --- /dev/null +++ b/mocks/mock_utils/mock_filesystem/mock_mount_client.go @@ -0,0 +1,69 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/netapp/trident/utils/filesystem (interfaces: Mount) +// +// Generated by this command: +// +// mockgen -destination=../../mocks/mock_utils/mock_filesystem/mock_mount_client.go github.com/netapp/trident/utils/filesystem Mount +// + +// Package mock_filesystem is a generated GoMock package. +package mock_filesystem + +import ( + context "context" + reflect "reflect" + + gomock "go.uber.org/mock/gomock" +) + +// MockMount is a mock of Mount interface. +type MockMount struct { + ctrl *gomock.Controller + recorder *MockMountMockRecorder +} + +// MockMountMockRecorder is the mock recorder for MockMount. +type MockMountMockRecorder struct { + mock *MockMount +} + +// NewMockMount creates a new mock instance. +func NewMockMount(ctrl *gomock.Controller) *MockMount { + mock := &MockMount{ctrl: ctrl} + mock.recorder = &MockMountMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockMount) EXPECT() *MockMountMockRecorder { + return m.recorder +} + +// MountFilesystemForResize mocks base method. +func (m *MockMount) MountFilesystemForResize(arg0 context.Context, arg1, arg2, arg3 string) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MountFilesystemForResize", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MountFilesystemForResize indicates an expected call of MountFilesystemForResize. +func (mr *MockMountMockRecorder) MountFilesystemForResize(arg0, arg1, arg2, arg3 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MountFilesystemForResize", reflect.TypeOf((*MockMount)(nil).MountFilesystemForResize), arg0, arg1, arg2, arg3) +} + +// RemoveMountPoint mocks base method. +func (m *MockMount) RemoveMountPoint(arg0 context.Context, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RemoveMountPoint", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// RemoveMountPoint indicates an expected call of RemoveMountPoint. +func (mr *MockMountMockRecorder) RemoveMountPoint(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveMountPoint", reflect.TypeOf((*MockMount)(nil).RemoveMountPoint), arg0, arg1) +} diff --git a/mocks/mock_utils/mock_iscsi/mock_iscsi_filesystem_client.go b/mocks/mock_utils/mock_iscsi/mock_iscsi_filesystem_client.go deleted file mode 100644 index 4e0a722b0..000000000 --- a/mocks/mock_utils/mock_iscsi/mock_iscsi_filesystem_client.go +++ /dev/null @@ -1,66 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/netapp/trident/utils/iscsi (interfaces: FileSystem) -// -// Generated by this command: -// -// mockgen -destination=../../mocks/mock_utils/mock_iscsi/mock_iscsi_filesystem_client.go github.com/netapp/trident/utils/iscsi FileSystem -// - -// Package mock_iscsi is a generated GoMock package. -package mock_iscsi - -import ( - context "context" - reflect "reflect" - - gomock "go.uber.org/mock/gomock" -) - -// MockFileSystem is a mock of FileSystem interface. -type MockFileSystem struct { - ctrl *gomock.Controller - recorder *MockFileSystemMockRecorder -} - -// MockFileSystemMockRecorder is the mock recorder for MockFileSystem. -type MockFileSystemMockRecorder struct { - mock *MockFileSystem -} - -// NewMockFileSystem creates a new mock instance. -func NewMockFileSystem(ctrl *gomock.Controller) *MockFileSystem { - mock := &MockFileSystem{ctrl: ctrl} - mock.recorder = &MockFileSystemMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockFileSystem) EXPECT() *MockFileSystemMockRecorder { - return m.recorder -} - -// FormatVolume mocks base method. -func (m *MockFileSystem) FormatVolume(arg0 context.Context, arg1, arg2, arg3 string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "FormatVolume", arg0, arg1, arg2, arg3) - ret0, _ := ret[0].(error) - return ret0 -} - -// FormatVolume indicates an expected call of FormatVolume. -func (mr *MockFileSystemMockRecorder) FormatVolume(arg0, arg1, arg2, arg3 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FormatVolume", reflect.TypeOf((*MockFileSystem)(nil).FormatVolume), arg0, arg1, arg2, arg3) -} - -// RepairVolume mocks base method. -func (m *MockFileSystem) RepairVolume(arg0 context.Context, arg1, arg2 string) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "RepairVolume", arg0, arg1, arg2) -} - -// RepairVolume indicates an expected call of RepairVolume. -func (mr *MockFileSystemMockRecorder) RepairVolume(arg0, arg1, arg2 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RepairVolume", reflect.TypeOf((*MockFileSystem)(nil).RepairVolume), arg0, arg1, arg2) -} diff --git a/storage_drivers/common.go b/storage_drivers/common.go index c6f5e3054..194a53b9f 100644 --- a/storage_drivers/common.go +++ b/storage_drivers/common.go @@ -17,6 +17,7 @@ import ( sa "github.com/netapp/trident/storage_attribute" "github.com/netapp/trident/utils" "github.com/netapp/trident/utils/errors" + "github.com/netapp/trident/utils/filesystem" tridentmodels "github.com/netapp/trident/utils/models" ) @@ -256,7 +257,7 @@ func Clone(ctx context.Context, source, destination interface{}) { // CheckSupportedFilesystem checks for a supported file system type func CheckSupportedFilesystem(ctx context.Context, fs, volumeInternalName string) (string, error) { - fsType, err := utils.VerifyFilesystemSupport(fs) + fsType, err := filesystem.VerifyFilesystemSupport(fs) if err == nil { Logc(ctx).WithFields(LogFields{ "fileSystemType": fsType, diff --git a/storage_drivers/ontap/ontap_common.go b/storage_drivers/ontap/ontap_common.go index 51a90c42d..103277141 100644 --- a/storage_drivers/ontap/ontap_common.go +++ b/storage_drivers/ontap/ontap_common.go @@ -35,6 +35,7 @@ import ( "github.com/netapp/trident/utils" "github.com/netapp/trident/utils/errors" "github.com/netapp/trident/utils/fcp" + "github.com/netapp/trident/utils/filesystem" tridentmodels "github.com/netapp/trident/utils/models" "github.com/netapp/trident/utils/version" ) @@ -893,7 +894,7 @@ func PublishLUN( } // xfs volumes are always mounted with '-o nouuid' to allow clones to be mounted to the same node as the source - if fstype == tridentconfig.FsXfs { + if fstype == filesystem.Xfs { publishInfo.MountOptions = drivers.EnsureMountOption(publishInfo.MountOptions, drivers.MountOptionNoUUID) } diff --git a/storage_drivers/ontap/ontap_san.go b/storage_drivers/ontap/ontap_san.go index 398a7d0c9..3b9c778c7 100644 --- a/storage_drivers/ontap/ontap_san.go +++ b/storage_drivers/ontap/ontap_san.go @@ -94,7 +94,7 @@ func (d *SANStorageDriver) Initialize( // Initialize the iSCSI client var err error - d.iscsi, err = iscsi.New(utils.NewOSClient(), utils.NewDevicesClient(), utils.NewFilesystemClient()) + d.iscsi, err = iscsi.New(utils.NewOSClient(), utils.NewDevicesClient()) if err != nil { return fmt.Errorf("could not initialize iSCSI client; %v", err) } diff --git a/storage_drivers/ontap/ontap_san_economy.go b/storage_drivers/ontap/ontap_san_economy.go index 7a102f568..0e810b350 100644 --- a/storage_drivers/ontap/ontap_san_economy.go +++ b/storage_drivers/ontap/ontap_san_economy.go @@ -252,7 +252,7 @@ func (d *SANEconomyStorageDriver) Initialize( // Initialize the iSCSI client var err error - d.iscsi, err = iscsi.New(utils.NewOSClient(), utils.NewDevicesClient(), utils.NewFilesystemClient()) + d.iscsi, err = iscsi.New(utils.NewOSClient(), utils.NewDevicesClient()) if err != nil { return fmt.Errorf("error initializing iSCSI client: %v", err) } diff --git a/storage_drivers/ontap/ontap_san_economy_test.go b/storage_drivers/ontap/ontap_san_economy_test.go index 3218820ba..099383a36 100644 --- a/storage_drivers/ontap/ontap_san_economy_test.go +++ b/storage_drivers/ontap/ontap_san_economy_test.go @@ -27,6 +27,7 @@ import ( "github.com/netapp/trident/storage_drivers/ontap/awsapi" "github.com/netapp/trident/utils" utilserrors "github.com/netapp/trident/utils/errors" + "github.com/netapp/trident/utils/filesystem" "github.com/netapp/trident/utils/iscsi" "github.com/netapp/trident/utils/models" ) @@ -195,7 +196,7 @@ func newTestOntapSanEcoDriver(t *testing.T, vserverAdminHost, vserverAdminPort, config.AWSConfig.FSxFilesystemID = *fsxId } - iscsiClient, err := iscsi.New(utils.NewOSClient(), utils.NewDevicesClient(), utils.NewFilesystemClient()) + iscsiClient, err := iscsi.New(utils.NewOSClient(), utils.NewDevicesClient()) assert.NoError(t, err) sanEcoDriver := &SANEconomyStorageDriver{ @@ -1532,7 +1533,8 @@ func TestOntapSanEconomyVolumeCreate_ResizeVolumeSizeFailed2(t *testing.T) { func TestOntapSanEconomyVolumeCreate_FormatOptions(t *testing.T) { mockAPI, d := newMockOntapSanEcoDriver(t) - tempFormatOptions := strings.Join([]string{"-b 4096", "-T stride=256, stripe=16"}, utils.FormatOptionsSeparator) + tempFormatOptions := strings.Join([]string{"-b 4096", "-T stride=256, stripe=16"}, + filesystem.FormatOptionsSeparator) pool1 := storage.NewStoragePool(nil, "pool1") pool1.SetInternalAttributes(map[string]string{ SpaceReserve: "none", diff --git a/storage_drivers/ontap/ontap_san_nvme.go b/storage_drivers/ontap/ontap_san_nvme.go index 9fa7dbf97..62ee05faa 100644 --- a/storage_drivers/ontap/ontap_san_nvme.go +++ b/storage_drivers/ontap/ontap_san_nvme.go @@ -25,6 +25,7 @@ import ( "github.com/netapp/trident/storage_drivers/ontap/awsapi" "github.com/netapp/trident/utils" "github.com/netapp/trident/utils/errors" + "github.com/netapp/trident/utils/filesystem" "github.com/netapp/trident/utils/models" ) @@ -818,7 +819,7 @@ func (d *NVMeStorageDriver) Publish( // When FS type is RAW, we create a new subsystem per namespace, // else we use the subsystem created for that particular node var ssName string - if volConfig.FileSystem == tridentconfig.FsRaw { + if volConfig.FileSystem == filesystem.Raw { ssName = getNamespaceSpecificSubsystemName(name, pvName) } else { ssName = getNodeSpecificSubsystemName(publishInfo.HostName, publishInfo.TridentUUID) diff --git a/storage_drivers/ontap/ontap_san_nvme_test.go b/storage_drivers/ontap/ontap_san_nvme_test.go index 8a6e48351..b1c9855f1 100644 --- a/storage_drivers/ontap/ontap_san_nvme_test.go +++ b/storage_drivers/ontap/ontap_san_nvme_test.go @@ -19,6 +19,7 @@ import ( "github.com/netapp/trident/storage_drivers/ontap/api" "github.com/netapp/trident/storage_drivers/ontap/awsapi" "github.com/netapp/trident/utils/errors" + "github.com/netapp/trident/utils/filesystem" "github.com/netapp/trident/utils/models" ) @@ -54,7 +55,7 @@ func newNVMeDriver(apiOverride api.OntapAPI, awsApiOverride awsapi.AWSAPI, fsxId } pool1 := storage.NewStoragePool(nil, "pool1") - pool1.InternalAttributes()[FileSystemType] = tridentconfig.FsExt4 + pool1.InternalAttributes()[FileSystemType] = filesystem.Ext4 driver.virtualPools = map[string]storage.Pool{"pool1": pool1} driver.physicalPools = map[string]storage.Pool{"pool1": pool1} @@ -1862,21 +1863,21 @@ func TestCreateClone(t *testing.T) { } pool := storage.NewStoragePool(nil, "fakepool") - pool.InternalAttributes()[FileSystemType] = tridentconfig.FsExt4 + pool.InternalAttributes()[FileSystemType] = filesystem.Ext4 pool.InternalAttributes()[SplitOnClone] = "true" pool.Attributes()["labels"] = sa.NewLabelOffer(map[string]string{ "type": "clone", }) poolWithBadName := storage.NewStoragePool(nil, "") - poolWithBadName.InternalAttributes()[FileSystemType] = tridentconfig.FsExt4 + poolWithBadName.InternalAttributes()[FileSystemType] = filesystem.Ext4 poolWithBadName.InternalAttributes()[SplitOnClone] = "true" poolWithBadName.Attributes()["labels"] = sa.NewLabelOffer(map[string]string{ "type": "clone", }) poolWithNameTemplate := storage.NewStoragePool(nil, "fakepool") - poolWithNameTemplate.InternalAttributes()[FileSystemType] = tridentconfig.FsExt4 + poolWithNameTemplate.InternalAttributes()[FileSystemType] = filesystem.Ext4 poolWithNameTemplate.InternalAttributes()[SplitOnClone] = "true" poolWithNameTemplate.Attributes()["labels"] = sa.NewLabelOffer(map[string]string{ "type": "clone", @@ -1900,7 +1901,7 @@ func TestCreateClone(t *testing.T) { "lthQFQfHVgPpUZdzZMjXry" poolWithLongLabelVal := storage.NewStoragePool(nil, "fakepool") - poolWithLongLabelVal.InternalAttributes()[FileSystemType] = tridentconfig.FsExt4 + poolWithLongLabelVal.InternalAttributes()[FileSystemType] = filesystem.Ext4 poolWithLongLabelVal.InternalAttributes()[SplitOnClone] = "true" poolWithLongLabelVal.Attributes()["labels"] = sa.NewLabelOffer(map[string]string{ "type": "clone", @@ -2256,7 +2257,7 @@ func TestImport_NameTemplate(t *testing.T) { ns.State = "online" pool := storage.NewStoragePool(nil, "fakepool") - pool.InternalAttributes()[FileSystemType] = tridentconfig.FsExt4 + pool.InternalAttributes()[FileSystemType] = filesystem.Ext4 pool.InternalAttributes()[SplitOnClone] = "true" pool.InternalAttributes()[NameTemplate] = "{{.config.StorageDriverName}}_{{.labels.Cluster}}_{{.volume.Namespace}}_{{.volume." + "RequestName}}" @@ -2301,7 +2302,7 @@ func TestImport_LongLabelError(t *testing.T) { labelMap := map[string]string{"key": longLabelVal} pool := storage.NewStoragePool(nil, "fakepool") - pool.InternalAttributes()[FileSystemType] = tridentconfig.FsExt4 + pool.InternalAttributes()[FileSystemType] = filesystem.Ext4 pool.InternalAttributes()[SplitOnClone] = "true" pool.InternalAttributes()[NameTemplate] = "{{.config.StorageDriverName}}_{{.labels.Cluster}}_{{.volume.Namespace}}_{{.volume." + "RequestName}}" diff --git a/storage_drivers/ontap/ontap_san_test.go b/storage_drivers/ontap/ontap_san_test.go index ad2493794..31f7d4366 100644 --- a/storage_drivers/ontap/ontap_san_test.go +++ b/storage_drivers/ontap/ontap_san_test.go @@ -25,6 +25,7 @@ import ( "github.com/netapp/trident/storage_drivers/ontap/awsapi" "github.com/netapp/trident/utils" "github.com/netapp/trident/utils/errors" + "github.com/netapp/trident/utils/filesystem" "github.com/netapp/trident/utils/iscsi" "github.com/netapp/trident/utils/models" ) @@ -71,7 +72,7 @@ func newMockOntapSANDriver(t *testing.T) (*mockapi.MockOntapAPI, *SANStorageDriv driver.API = mockAPI driver.ips = []string{"127.0.0.1"} - iscsiClient, err := iscsi.New(utils.NewOSClient(), utils.NewDevicesClient(), utils.NewFilesystemClient()) + iscsiClient, err := iscsi.New(utils.NewOSClient(), utils.NewDevicesClient()) assert.NoError(t, err) driver.iscsi = iscsiClient @@ -1254,7 +1255,8 @@ func TestOntapSanVolumeCreate_FormatOptions(t *testing.T) { fsType := "xfs" // Setting up what formatOptions could look like. - tempFormatOptions := strings.Join([]string{"-b 4096", "-T stirde=2056, stripe=16"}, utils.FormatOptionsSeparator) + tempFormatOptions := strings.Join([]string{"-b 4096", "-T stirde=2056, stripe=16"}, + filesystem.FormatOptionsSeparator) pool1 := storage.NewStoragePool(nil, "pool1") pool1.SetInternalAttributes(map[string]string{ diff --git a/storage_drivers/solidfire/solidfire_san.go b/storage_drivers/solidfire/solidfire_san.go index 9751ecaaa..eb6566b02 100644 --- a/storage_drivers/solidfire/solidfire_san.go +++ b/storage_drivers/solidfire/solidfire_san.go @@ -25,6 +25,7 @@ import ( "github.com/netapp/trident/storage_drivers/solidfire/api" "github.com/netapp/trident/utils" "github.com/netapp/trident/utils/errors" + "github.com/netapp/trident/utils/filesystem" "github.com/netapp/trident/utils/iscsi" "github.com/netapp/trident/utils/models" ) @@ -144,7 +145,7 @@ func (d *SANStorageDriver) Initialize( // Initialize the iSCSI client var err error - d.iscsi, err = iscsi.New(utils.NewOSClient(), utils.NewDevicesClient(), utils.NewFilesystemClient()) + d.iscsi, err = iscsi.New(utils.NewOSClient(), utils.NewDevicesClient()) if err != nil { return fmt.Errorf("could not initialize iSCSI client: %v", err) } @@ -1230,7 +1231,7 @@ func (d *SANStorageDriver) Publish( } // xfs volumes are always mounted with '-o nouuid' to allow clones to be mounted to the same node as the source - if fstype == tridentconfig.FsXfs { + if fstype == filesystem.Xfs { publishInfo.MountOptions = drivers.EnsureMountOption(publishInfo.MountOptions, drivers.MountOptionNoUUID) } diff --git a/utils/adaptors.go b/utils/adaptors.go index d9dcc5d8d..192999771 100644 --- a/utils/adaptors.go +++ b/utils/adaptors.go @@ -138,17 +138,3 @@ func (c DevicesClient) NewLUKSDeviceFromMappingPath(ctx context.Context, mapping ) (models.LUKSDeviceInterface, error) { return NewLUKSDeviceFromMappingPath(ctx, mappingPath, volumeId) } - -type FilesystemClient struct{} - -func NewFilesystemClient() FilesystemClient { - return FilesystemClient{} -} - -func (c FilesystemClient) FormatVolume(ctx context.Context, device, fstype, options string) error { - return formatVolume(ctx, device, fstype, options) -} - -func (c FilesystemClient) RepairVolume(ctx context.Context, device, fstype string) { - repairVolume(ctx, device, fstype) -} diff --git a/utils/csiutils_windows.go b/utils/csiutils/csiutils_windows.go similarity index 99% rename from utils/csiutils_windows.go rename to utils/csiutils/csiutils_windows.go index 94eac8768..ba010f854 100644 --- a/utils/csiutils_windows.go +++ b/utils/csiutils/csiutils_windows.go @@ -1,7 +1,7 @@ // Copyright 2022 NetApp, Inc. All Rights Reserved. // This file contains the code derived from https://github.com/kubernetes-csi/csi-driver-smb/blob/master/pkg/mounter/safe_mounter_windows.go -package utils +package csiutils import ( "context" diff --git a/utils/csiutils_windows_test.go b/utils/csiutils/csiutils_windows_test.go similarity index 94% rename from utils/csiutils_windows_test.go rename to utils/csiutils/csiutils_windows_test.go index a00694635..91e6dce88 100644 --- a/utils/csiutils_windows_test.go +++ b/utils/csiutils/csiutils_windows_test.go @@ -1,6 +1,6 @@ // Copyright 2022 NetApp, Inc. All Rights Reserved. -package utils +package csiutils import ( "testing" diff --git a/utils/csiutils_windows_types.go b/utils/csiutils/csiutils_windows_types.go similarity index 81% rename from utils/csiutils_windows_types.go rename to utils/csiutils/csiutils_windows_types.go index 0bb01bb5d..2e810640c 100644 --- a/utils/csiutils_windows_types.go +++ b/utils/csiutils/csiutils_windows_types.go @@ -1,6 +1,6 @@ // Copyright 2022 NetApp, Inc. All Rights Reserved. -package utils +package csiutils import ( "context" @@ -8,7 +8,7 @@ import ( "k8s.io/mount-utils" ) -//go:generate mockgen -destination=../mocks/mock_utils/mock_csiutils.go github.com/netapp/trident/utils CSIProxyUtils +//go:generate mockgen -destination=../mocks/mock_csiutils/mock_csiutils.go github.com/netapp/trident/csiutils CSIProxyUtils type CSIProxyUtils interface { SMBMount(context.Context, string, string, string, string, string) error diff --git a/utils/devices_linux.go b/utils/devices_linux.go index 2c1901328..306301a02 100644 --- a/utils/devices_linux.go +++ b/utils/devices_linux.go @@ -14,6 +14,7 @@ import ( "unsafe" log "github.com/sirupsen/logrus" + "github.com/spf13/afero" "golang.org/x/net/context" "golang.org/x/sys/unix" @@ -46,6 +47,8 @@ var ( beforeCryptSetupFormat = fiji.Register("beforeCryptSetupFormat", "node_server") duringOpenBeforeCryptSetupOpen = fiji.Register("duringOpenBeforeCryptSetupOpen", "devices_linux") duringRotatePassphraseBeforeLuksKeyChange = fiji.Register("duringRotatePassphraseBeforeLuksKeyChange", "devices_linux") + + osFs = afero.NewOsFs() ) // flushOneDevice flushes any outstanding I/O to a disk @@ -476,7 +479,7 @@ func (d *LUKSDevice) RotatePassphrase( // Write the old passphrase to an anonymous file in memory because we can't provide it on stdin tempFileName := fmt.Sprintf("luks_key_%s", volumeId) - fd, err := generateAnonymousMemFile(tempFileName, previousLUKSPassphrase) + fd, err := fsClient.GenerateAnonymousMemFile(tempFileName, previousLUKSPassphrase) if err != nil { Log().WithFields(LogFields{ "error": err, diff --git a/utils/fcp.go b/utils/fcp.go index 1badf4e99..0b062d95c 100644 --- a/utils/fcp.go +++ b/utils/fcp.go @@ -1,9 +1,12 @@ package utils -import "github.com/netapp/trident/utils/fcp" +import ( + "github.com/netapp/trident/utils/fcp" + "github.com/netapp/trident/utils/filesystem" +) var ( FcpUtils = fcp.NewReconcileUtils(chrootPathPrefix, NewOSClient()) fcpClient = fcp.NewDetailed(chrootPathPrefix, command, fcp.DefaultSelfHealingExclusion, NewOSClient(), - NewDevicesClient(), NewFilesystemClient(), mountClient, FcpUtils) + NewDevicesClient(), filesystem.New(mountClient), mountClient, FcpUtils) ) diff --git a/utils/fcp/fcp.go b/utils/fcp/fcp.go index 859c1f008..71b49ca64 100644 --- a/utils/fcp/fcp.go +++ b/utils/fcp/fcp.go @@ -17,13 +17,12 @@ import ( "github.com/cenkalti/backoff/v4" - "github.com/netapp/trident/config" "github.com/netapp/trident/utils/mount" - // "github.com/netapp/trident/internal/fiji" . "github.com/netapp/trident/logging" "github.com/netapp/trident/utils/errors" tridentexec "github.com/netapp/trident/utils/exec" + "github.com/netapp/trident/utils/filesystem" "github.com/netapp/trident/utils/models" ) @@ -95,8 +94,8 @@ type Client struct { selfHealingExclusion []string osClient OS deviceClient Devices - fileSystemClient FileSystem - mountClient Mount + fs FileSystem + mount Mount fcpUtils FcpReconcileUtils } @@ -119,7 +118,7 @@ func New(osClient OS, deviceClient Devices, fileSystemClient FileSystem) (*Clien func NewDetailed( chrootPathPrefix string, command tridentexec.Command, selfHealingExclusion []string, - osClient OS, deviceClient Devices, fileSystemClient FileSystem, mountClient Mount, + osClient OS, deviceClient Devices, fs FileSystem, mount Mount, fcpUtils FcpReconcileUtils, ) *Client { return &Client{ @@ -127,8 +126,8 @@ func NewDetailed( command: command, osClient: osClient, deviceClient: deviceClient, - fileSystemClient: fileSystemClient, - mountClient: mountClient, + fs: fs, + mount: mount, fcpUtils: fcpUtils, selfHealingExclusion: selfHealingExclusion, } @@ -488,7 +487,7 @@ func (client *Client) AttachVolume( // Return the device in the publish info in case the mount will be done later publishInfo.DevicePath = devicePath - if publishInfo.FilesystemType == config.FsRaw { + if publishInfo.FilesystemType == filesystem.Raw { return mpathSize, nil } @@ -516,7 +515,7 @@ func (client *Client) AttachVolume( } Logc(ctx).WithFields(LogFields{"volume": name, "fstype": publishInfo.FilesystemType}).Debug("Formatting LUN.") - err := client.fileSystemClient.FormatVolume(ctx, devicePath, publishInfo.FilesystemType, publishInfo.FormatOptions) + err := client.fs.FormatVolume(ctx, devicePath, publishInfo.FilesystemType, publishInfo.FormatOptions) if err != nil { return mpathSize, fmt.Errorf("error formatting LUN %s, device %s: %v", name, deviceToUse, err) } @@ -539,17 +538,17 @@ func (client *Client) AttachVolume( // in-use volumes, or creating volumes from snapshots taken from in-use volumes. This is only safe to do // if a device is not mounted. The fsck command returns a non-zero exit code if filesystem errors are found, // even if they are completely and automatically fixed, so we don't return any error here. - mounted, err := client.mountClient.IsMounted(ctx, devicePath, "", "") + mounted, err := client.mount.IsMounted(ctx, devicePath, "", "") if err != nil { return mpathSize, err } if !mounted { - client.fileSystemClient.RepairVolume(ctx, devicePath, publishInfo.FilesystemType) + client.fs.RepairVolume(ctx, devicePath, publishInfo.FilesystemType) } // Optionally mount the device if mountPoint != "" { - if err := client.mountClient.MountDevice(ctx, devicePath, mountPoint, publishInfo.MountOptions, + if err := client.mount.MountDevice(ctx, devicePath, mountPoint, publishInfo.MountOptions, false); err != nil { return mpathSize, fmt.Errorf("error mounting LUN %v, device %v, mountpoint %v; %s", name, deviceToUse, mountPoint, err) diff --git a/utils/filesystem.go b/utils/filesystem/filesystem.go similarity index 63% rename from utils/filesystem.go rename to utils/filesystem/filesystem.go index 5fabc6a8e..2d00b6e5e 100644 --- a/utils/filesystem.go +++ b/utils/filesystem/filesystem.go @@ -1,12 +1,13 @@ // Copyright 2024 NetApp, Inc. All Rights Reserved. -package utils +package filesystem + +//go:generate mockgen -destination=../../mocks/mock_utils/mock_filesystem/mock_mount_client.go github.com/netapp/trident/utils/filesystem Mount +//go:generate mockgen -destination=../../mocks/mock_utils/mock_filesystem/mock_filesystem_client.go github.com/netapp/trident/utils/filesystem Filesystem import ( "context" - "encoding/json" "fmt" - "io/fs" "os" "os/exec" "strings" @@ -18,16 +19,16 @@ import ( "github.com/netapp/trident/internal/fiji" . "github.com/netapp/trident/logging" - "github.com/netapp/trident/utils/errors" + tridentexec "github.com/netapp/trident/utils/exec" "github.com/netapp/trident/utils/models" ) const ( // Filesystem types - fsXfs = "xfs" - fsExt3 = "ext3" - fsExt4 = "ext4" - fsRaw = "raw" + Xfs = "xfs" + Ext3 = "ext3" + Ext4 = "ext4" + Raw = "raw" ) const ( @@ -40,39 +41,64 @@ const ( fsckSyntaxError = 16 fsckCancelledByUser = 32 fsckSharedLibError = 128 -) -const FormatOptionsSeparator = " " + FormatOptionsSeparator = " " +) var ( - osFs = afero.NewOsFs() - JsonReaderWriter = NewJSONReaderWriter() + formatVolumeMaxRetryDuration = 30 * time.Second duringFormatVolume = fiji.Register("duringFormatVolume", "filesystem") duringRepairVolume = fiji.Register("duringRepairVolume", "filesystem") ) -type jsonReaderWriter struct{} +type Filesystem interface { + GetDFOutput(ctx context.Context) ([]models.DFInfo, error) + FormatVolume(ctx context.Context, device, fstype, options string) error + RepairVolume(ctx context.Context, device, fstype string) + ExpandFilesystemOnNode( + ctx context.Context, publishInfo *models.VolumePublishInfo, devicePath, stagedTargetPath, fsType, mountOptions string, + ) (int64, error) + DeleteFile(ctx context.Context, filepath, fileDescription string) (string, error) + GetFilesystemStats( + ctx context.Context, path string, + ) (available, capacity, usage, inodes, inodesFree, inodesUsed int64, err error) + GetUnmountPath(ctx context.Context, trackingInfo *models.VolumeTrackingInfo) (string, error) + GenerateAnonymousMemFile(tempFileName, content string) (int, error) +} + +type Mount interface { + MountFilesystemForResize(ctx context.Context, devicePath, stagedTargetPath, mountOptions string) (string, error) + RemoveMountPoint(ctx context.Context, mountPointPath string) error +} -func NewJSONReaderWriter() models.JSONReaderWriter { - return &jsonReaderWriter{} +type FSClient struct { + command tridentexec.Command + osFs afero.Fs + mountClient Mount } -// DFInfo data structure for wrapping the parsed output from the 'df' command -type DFInfo struct { - Target string - Source string +func New(mount Mount) *FSClient { + return NewDetailed(tridentexec.NewCommand(), afero.NewOsFs(), mount) +} + +func NewDetailed(command tridentexec.Command, osFs afero.Fs, mount Mount) *FSClient { + return &FSClient{ + command: command, + osFs: osFs, + mountClient: mount, + } } // GetDFOutput returns parsed DF output -func GetDFOutput(ctx context.Context) ([]DFInfo, error) { +func (f *FSClient) GetDFOutput(ctx context.Context) ([]models.DFInfo, error) { GenerateRequestContextForLayer(ctx, LogLayerUtils) Logc(ctx).Debug(">>>> filesystem.GetDFOutput") defer Logc(ctx).Debug("<<<< filesystem.GetDFOutput") - var result []DFInfo - out, err := command.Execute(ctx, "df", "--output=target,source") + var result []models.DFInfo + out, err := f.command.Execute(ctx, "df", "--output=target,source") if err != nil { // df returns an error if there's a stale file handle that we can // safely ignore. There may be other reasons. Consider it a warning if @@ -88,7 +114,7 @@ func GetDFOutput(ctx context.Context) ([]DFInfo, error) { a := strings.Fields(l) if len(a) > 1 { - result = append(result, DFInfo{ + result = append(result, models.DFInfo{ Target: a[0], Source: a[1], }) @@ -100,8 +126,8 @@ func GetDFOutput(ctx context.Context) ([]DFInfo, error) { return result, nil } -// formatVolume creates a filesystem for the supplied device of the supplied type. -func formatVolume(ctx context.Context, device, fstype, options string) error { +// FormatVolume creates a filesystem for the supplied device of the supplied type. +func (f *FSClient) FormatVolume(ctx context.Context, device, fstype, options string) error { logFields := LogFields{"device": device, "fsType": fstype} Logc(ctx).WithFields(logFields).Debug(">>>> filesystem.formatVolume") defer Logc(ctx).WithFields(logFields).Debug("<<<< filesystem.formatVolume") @@ -119,15 +145,15 @@ func formatVolume(ctx context.Context, device, fstype, options string) error { } switch fstype { - case fsXfs: + case Xfs: optionList = append(optionList, "-f", device) - out, err = command.Execute(ctx, "mkfs.xfs", optionList...) - case fsExt3: + out, err = f.command.Execute(ctx, "mkfs.xfs", optionList...) + case Ext3: optionList = append(optionList, "-F", device) - out, err = command.Execute(ctx, "mkfs.ext3", optionList...) - case fsExt4: + out, err = f.command.Execute(ctx, "mkfs.ext3", optionList...) + case Ext4: optionList = append(optionList, "-F", device) - out, err = command.Execute(ctx, "mkfs.ext4", optionList...) + out, err = f.command.Execute(ctx, "mkfs.ext4", optionList...) default: return fmt.Errorf("unsupported file system type: %s", fstype) } @@ -144,21 +170,21 @@ func formatVolume(ctx context.Context, device, fstype, options string) error { } // formatVolume creates a filesystem for the supplied device of the supplied type. -func formatVolumeRetry(ctx context.Context, device, fstype, options string) error { +func (f *FSClient) formatVolumeRetry(ctx context.Context, device, fstype, options string) error { logFields := LogFields{"device": device, "fsType": fstype} Logc(ctx).WithFields(logFields).Debug(">>>> filesystem.formatVolumeRetry") defer Logc(ctx).WithFields(logFields).Debug("<<<< filesystem.formatVolumeRetry") - maxDuration := 30 * time.Second - formatVolume := func() error { - return formatVolume(ctx, device, fstype, options) + return f.FormatVolume(ctx, device, fstype, options) } formatNotify := func(err error, duration time.Duration) { Logc(ctx).WithField("increment", duration).Debug("Format failed, retrying.") } + maxDuration := formatVolumeMaxRetryDuration + formatBackoff := backoff.NewExponentialBackOff() formatBackoff.InitialInterval = 2 * time.Second formatBackoff.Multiplier = 2 @@ -175,21 +201,26 @@ func formatVolumeRetry(ctx context.Context, device, fstype, options string) erro return nil } -// repairVolume runs fsck on a volume. This is best-effort, does not return error. -func repairVolume(ctx context.Context, device, fstype string) { +// RepairVolume runs fsck on a volume. This is best-effort, does not return error. +func (f *FSClient) RepairVolume(ctx context.Context, device, fstype string) { logFields := LogFields{"device": device, "fsType": fstype} Logc(ctx).WithFields(logFields).Debug(">>>> filesystem.repairVolume") defer Logc(ctx).WithFields(logFields).Debug("<<<< filesystem.repairVolume") + // Note: FIJI error injection will have no effect due to no error return. Panic injection is still valid. + if err := duringRepairVolume.Inject(); err != nil { + Logc(ctx).WithError(err).Debug("FIJI error in repairVolume has no effect, no error return.") + } + var err error switch fstype { case "xfs": break // fsck.xfs does nothing case "ext3": - _, err = command.Execute(ctx, "fsck.ext3", "-p", device) + _, err = f.command.Execute(ctx, "fsck.ext3", "-p", device) case "ext4": - _, err = command.Execute(ctx, "fsck.ext4", "-p", device) + _, err = f.command.Execute(ctx, "fsck.ext4", "-p", device) default: Logc(ctx).WithFields(logFields).Errorf("Unsupported file system type: %s.", fstype) } @@ -216,7 +247,7 @@ func repairVolume(ctx context.Context, device, fstype string) { } // ExpandFilesystemOnNode will expand the filesystem of an already expanded volume. -func ExpandFilesystemOnNode( +func (f *FSClient) ExpandFilesystemOnNode( ctx context.Context, publishInfo *models.VolumePublishInfo, devicePath, stagedTargetPath, fsType, mountOptions string, ) (int64, error) { GenerateRequestContextForLayer(ctx, LogLayerUtils) @@ -235,12 +266,13 @@ func ExpandFilesystemOnNode( defer Logc(ctx).WithFields(logFields).Debug("<<<< filesystem.ExpandFilesystemOnNode") if expansionMountPoint == "" { - expansionMountPoint, err = mountClient.MountFilesystemForResize(ctx, devicePath, stagedTargetPath, mountOptions) + expansionMountPoint, err = f.mountClient.MountFilesystemForResize(ctx, devicePath, stagedTargetPath, + mountOptions) if err != nil { return 0, err } defer func() { - err = multierr.Append(err, mountClient.RemoveMountPoint(ctx, expansionMountPoint)) + err = multierr.Append(err, f.mountClient.RemoveMountPoint(ctx, expansionMountPoint)) }() } @@ -249,9 +281,9 @@ func ExpandFilesystemOnNode( var size int64 switch fsType { case "xfs": - size, err = expandFilesystem(ctx, "xfs_growfs", expansionMountPoint, expansionMountPoint) + size, err = f.expandFilesystem(ctx, "xfs_growfs", expansionMountPoint, expansionMountPoint) case "ext3", "ext4": - size, err = expandFilesystem(ctx, "resize2fs", devicePath, expansionMountPoint) + size, err = f.expandFilesystem(ctx, "resize2fs", devicePath, expansionMountPoint) default: err = fmt.Errorf("unsupported file system type: %s", fsType) } @@ -261,7 +293,7 @@ func ExpandFilesystemOnNode( return size, err } -func expandFilesystem(ctx context.Context, cmd, cmdArguments, tmpMountPoint string) (int64, error) { +func (f *FSClient) expandFilesystem(ctx context.Context, cmd, cmdArguments, tmpMountPoint string) (int64, error) { logFields := LogFields{ "cmd": cmd, "cmdArguments": cmdArguments, @@ -270,17 +302,17 @@ func expandFilesystem(ctx context.Context, cmd, cmdArguments, tmpMountPoint stri Logc(ctx).WithFields(logFields).Debug(">>>> filesystem.expandFilesystem") defer Logc(ctx).WithFields(logFields).Debug("<<<< filesystem.expandFilesystem") - preExpandSize, err := getFilesystemSize(ctx, tmpMountPoint) + preExpandSize, err := f.getFilesystemSize(ctx, tmpMountPoint) if err != nil { return 0, err } - _, err = command.Execute(ctx, cmd, cmdArguments) + _, err = f.command.Execute(ctx, cmd, cmdArguments) if err != nil { Logc(ctx).Errorf("Expanding filesystem failed; %s", err) return 0, err } - postExpandSize, err := getFilesystemSize(ctx, tmpMountPoint) + postExpandSize, err := f.getFilesystemSize(ctx, tmpMountPoint) if err != nil { return 0, err } @@ -292,71 +324,9 @@ func expandFilesystem(ctx context.Context, cmd, cmdArguments, tmpMountPoint stri return postExpandSize, nil } -// WriteJSONFile writes the contents of any type of struct to a file, with logging. -func (j jsonReaderWriter) WriteJSONFile( - ctx context.Context, fileContents interface{}, filepath, fileDescription string, -) error { - file, err := osFs.OpenFile(filepath, os.O_WRONLY|os.O_CREATE, 0o600) - if err != nil { - return err - } - defer func() { _ = file.Close() }() - - if err = json.NewEncoder(file).Encode(fileContents); err != nil { - Logc(ctx).WithFields(LogFields{ - "filename": filepath, - "error": err.Error(), - }).Error(fmt.Sprintf("Unable to write %s file.", fileDescription)) - return err - } - - return nil -} - -// ReadJSONFile reads a file at the specified path and deserializes its contents into the provided fileContents var. -// fileContents must be a pointer to a struct, not a pointer type! -func (j *jsonReaderWriter) ReadJSONFile( - ctx context.Context, fileContents interface{}, filepath, fileDescription string, -) error { - file, err := osFs.Open(filepath) - if err != nil { - if errors.Is(err, fs.ErrNotExist) { - Logc(ctx).WithFields(LogFields{ - "filepath": filepath, - "error": err.Error(), - }).Warningf("Could not find JSON file: %s.", filepath) - return errors.NotFoundError(err.Error()) - } - return err - } - defer func() { _ = file.Close() }() - - fileInfo, err := file.Stat() - if err != nil { - return err - } - // We do not consider an empty file valid JSON. - if fileInfo.Size() == 0 { - return errors.InvalidJSONError("file was empty, which is not considered valid JSON") - } - - err = json.NewDecoder(file).Decode(fileContents) - if err != nil { - Logc(ctx).WithFields(LogFields{ - "filename": filepath, - "error": err.Error(), - }).Error(fmt.Sprintf("Could not parse %s file.", fileDescription)) - - e, _ := errors.AsInvalidJSONError(err) - return e - } - - return nil -} - // DeleteFile deletes the file at the provided path, and provides additional logging. -func DeleteFile(ctx context.Context, filepath, fileDescription string) (string, error) { - if err := osFs.Remove(filepath); err != nil { +func (f *FSClient) DeleteFile(ctx context.Context, filepath, fileDescription string) (string, error) { + if err := f.osFs.Remove(filepath); err != nil { logFields := LogFields{strings.ReplaceAll(fileDescription, " ", ""): filepath, "error": err} if os.IsNotExist(err) { diff --git a/utils/filesystem_darwin.go b/utils/filesystem/filesystem_darwin.go similarity index 68% rename from utils/filesystem_darwin.go rename to utils/filesystem/filesystem_darwin.go index 02603f778..9b2c38612 100644 --- a/utils/filesystem_darwin.go +++ b/utils/filesystem/filesystem_darwin.go @@ -2,7 +2,7 @@ // NOTE: This file should only contain functions for handling the filesystem for Darwin flavor -package utils +package filesystem import ( "golang.org/x/net/context" @@ -13,14 +13,14 @@ import ( ) // GetFilesystemStats unused stub function -func GetFilesystemStats(ctx context.Context, _ string) (int64, int64, int64, int64, int64, int64, error) { +func (f *FSClient) GetFilesystemStats(ctx context.Context, _ string) (int64, int64, int64, int64, int64, int64, error) { Logc(ctx).Debug(">>>> filesystem_darwin.GetFilesystemStats") defer Logc(ctx).Debug("<<<< filesystem_darwin.GetFilesystemStats") return 0, 0, 0, 0, 0, 0, errors.UnsupportedError("GetFilesystemStats is not supported for darwin") } // getFilesystemSize unused stub function -func getFilesystemSize(ctx context.Context, _ string) (int64, error) { +func (f *FSClient) getFilesystemSize(ctx context.Context, _ string) (int64, error) { Logc(ctx).Debug(">>>> filesystem_darwin.getFilesystemSize") defer Logc(ctx).Debug("<<<< filesystem_darwin.getFilesystemSize") return 0, errors.UnsupportedError("getFilesystemSize is not supported for darwin") @@ -35,9 +35,17 @@ func GetDeviceFilePath(ctx context.Context, _, volumeId string) (string, error) } // GetUnmountPath is a dummy added for compilation. -func GetUnmountPath(ctx context.Context, trackingInfo *models.VolumeTrackingInfo) (string, error) { +func (f *FSClient) GetUnmountPath(ctx context.Context, trackingInfo *models.VolumeTrackingInfo) (string, error) { Logc(ctx).Debug(">>>> filesystem_darwin.GetUnmountPath") defer Logc(ctx).Debug("<<<< filesystem_darwin.GetUnmountPath") return "", errors.UnsupportedError("GetUnmountPath is not supported for darwin") } + +func (f *FSClient) GenerateAnonymousMemFile(tempFileName, content string) (int, error) { + ctx := context.Background() + Logc(ctx).Debug(">>>> filesystem_darwin.generateAnonymousMemFile") + defer Logc(ctx).Debug("<<<< filesystem_darwin.generateAnonymousMemFile") + + return 0, errors.UnsupportedError("generateAnonymousMemFile is not supported for darwin") +} diff --git a/utils/filesystem_darwin_test.go b/utils/filesystem/filesystem_darwin_test.go similarity index 74% rename from utils/filesystem_darwin_test.go rename to utils/filesystem/filesystem_darwin_test.go index 0c8aea0bc..5fc15257e 100644 --- a/utils/filesystem_darwin_test.go +++ b/utils/filesystem/filesystem_darwin_test.go @@ -1,6 +1,6 @@ // Copyright 2022 NetApp, Inc. All Rights Reserved. -package utils +package filesystem import ( "context" @@ -14,8 +14,9 @@ import ( func TestGetFilesystemSize(t *testing.T) { ctx := context.Background() + fsClient := New(nil) - result, err := getFilesystemSize(ctx, "") + result, err := fsClient.getFilesystemSize(ctx, "") assert.Equal(t, result, int64(0), "got non-zero filesystem size") assert.Error(t, err, "no error") assert.True(t, errors.IsUnsupportedError(err), "not UnsupportedError") @@ -23,8 +24,9 @@ func TestGetFilesystemSize(t *testing.T) { func TestGetFilesystemStats(t *testing.T) { ctx := context.Background() + fsClient := New(nil) - result1, result2, result3, result4, result5, result6, err := GetFilesystemStats(ctx, "") + result1, result2, result3, result4, result5, result6, err := fsClient.GetFilesystemStats(ctx, "") assert.Equal(t, result1, int64(0), "got non-zero available size") assert.Equal(t, result2, int64(0), "got non-zero capacity") assert.Equal(t, result3, int64(0), "got non-zero usage") @@ -35,20 +37,20 @@ func TestGetFilesystemStats(t *testing.T) { assert.True(t, errors.IsUnsupportedError(err), "not UnsupportedError") } -func TestGetDeviceFilePath(t *testing.T) { +func TestGetUnmountPath(t *testing.T) { ctx := context.Background() + fsClient := New(nil) - result, err := GetDeviceFilePath(ctx, "", "") - assert.Equal(t, result, "", "got device file path") + result, err := fsClient.GetUnmountPath(ctx, &models.VolumeTrackingInfo{}) + assert.Equal(t, result, "", "got unmount path") assert.Error(t, err, "no error") assert.True(t, errors.IsUnsupportedError(err), "not UnsupportedError") } -func TestGetUnmountPath(t *testing.T) { - ctx := context.Background() - - result, err := GetUnmountPath(ctx, &models.VolumeTrackingInfo{}) - assert.Equal(t, result, "", "got unmount path") +func TestGenerateAnonymousMemFile(t *testing.T) { + fsClient := New(nil) + result, err := fsClient.GenerateAnonymousMemFile("", "") + assert.Equal(t, 0, result) assert.Error(t, err, "no error") assert.True(t, errors.IsUnsupportedError(err), "not UnsupportedError") } diff --git a/utils/filesystem_linux.go b/utils/filesystem/filesystem_linux.go similarity index 87% rename from utils/filesystem_linux.go rename to utils/filesystem/filesystem_linux.go index 51fd0f4b3..02cdfbbfc 100644 --- a/utils/filesystem_linux.go +++ b/utils/filesystem/filesystem_linux.go @@ -2,7 +2,7 @@ // This file should only contain functions for handling the filesystem for Linux flavor -package utils +package filesystem import ( "context" @@ -16,9 +16,14 @@ import ( "github.com/netapp/trident/utils/models" ) +type statFSResult struct { + Output unix.Statfs_t + Error error +} + // GetFilesystemStats returns the size of the filesystem for the given path. // The caller of the func is responsible for verifying the mountPoint existence and readiness. -func GetFilesystemStats( +func (f *FSClient) GetFilesystemStats( ctx context.Context, path string, ) (available, capacity, usage, inodes, inodesFree, inodesUsed int64, err error) { Logc(ctx).Debug(">>>> filesystem_linux.GetFilesystemStats") @@ -77,11 +82,11 @@ func GetFilesystemStats( // getFilesystemSize returns the size of the filesystem for the given path. // The caller of the func is responsible for verifying the mountPoint existence and readiness. -func getFilesystemSize(ctx context.Context, path string) (int64, error) { +func (f *FSClient) getFilesystemSize(ctx context.Context, path string) (int64, error) { Logc(ctx).Debug(">>>> filesystem_linux.getFilesystemSize") defer Logc(ctx).Debug("<<<< filesystem_linux.getFilesystemSize") - _, size, _, _, _, _, err := GetFilesystemStats(ctx, path) + _, size, _, _, _, _, err := f.GetFilesystemStats(ctx, path) if err != nil { return 0, err } @@ -98,16 +103,16 @@ func GetDeviceFilePath(ctx context.Context, path, volumeId string) (string, erro } // GetUnmountPath is a dummy added for compilation -func GetUnmountPath(ctx context.Context, trackingInfo *models.VolumeTrackingInfo) (string, error) { +func (f *FSClient) GetUnmountPath(ctx context.Context, trackingInfo *models.VolumeTrackingInfo) (string, error) { Logc(ctx).Debug(">>>> filesystem_linux.GetUnmountPath") defer Logc(ctx).Debug("<<<< filesystem_linux.GetUnmountPath") return "", errors.UnsupportedError("GetUnmountPath is not supported for linux") } -// generateAnonymousMemFile uses linux syscall memfd_create to create an anonymous, temporary, in-memory file +// GenerateAnonymousMemFile uses linux syscall memfd_create to create an anonymous, temporary, in-memory file // with the specified name and contents -func generateAnonymousMemFile(tempFileName, content string) (int, error) { +func (f *FSClient) GenerateAnonymousMemFile(tempFileName, content string) (int, error) { fd, err := unix.MemfdCreate(tempFileName, 0) if err != nil { return -1, fmt.Errorf("failed to create anonymous file; %v", err) diff --git a/utils/filesystem/filesystem_linux_test.go b/utils/filesystem/filesystem_linux_test.go new file mode 100644 index 000000000..36470b328 --- /dev/null +++ b/utils/filesystem/filesystem_linux_test.go @@ -0,0 +1,188 @@ +// Copyright 2022 NetApp, Inc. All Rights Reserved. + +package filesystem + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/mock/gomock" + "golang.org/x/sys/unix" + + "github.com/netapp/trident/mocks/mock_utils/mock_exec" + "github.com/netapp/trident/mocks/mock_utils/mock_filesystem" + "github.com/netapp/trident/utils/errors" + execCmd "github.com/netapp/trident/utils/exec" + "github.com/netapp/trident/utils/models" +) + +func TestGetUnmountPath(t *testing.T) { + ctx := context.Background() + fsClient := New(nil) + + result, err := fsClient.GetUnmountPath(ctx, &models.VolumeTrackingInfo{}) + assert.Equal(t, result, "", "got unmount path") + assert.Error(t, err, "no error") + assert.True(t, errors.IsUnsupportedError(err), "not UnsupportedError") +} + +func TestGenerateAnonymousMemFile(t *testing.T) { + fsClient := &FSClient{} + tempFileName := "testFile" + content := "testContent" + + fd, err := fsClient.GenerateAnonymousMemFile(tempFileName, content) + assert.NoError(t, err, "expected no error creating anonymous mem file") + assert.Greater(t, fd, 0, "expected valid file descriptor") + + // Read back the content to verify + readContent := make([]byte, len(content)) + _, err = unix.Read(fd, readContent) + assert.NoError(t, err, "expected no error reading anonymous mem file") + assert.Equal(t, content, string(readContent), "expected content to match") + + // Close the file descriptor + err = unix.Close(fd) + assert.NoError(t, err, "expected no error closing anonymous mem file") +} + +func TestExpandFilesystemOnNode(t *testing.T) { + mockVolumeInfo := &models.VolumePublishInfo{ + DevicePath: "/mock/path", + } + + type parameters struct { + publishInfo *models.VolumePublishInfo + sharedTargetPath string + fsType string + mountOptions string + err error + getMountClient func() Mount + getCommandClient func() execCmd.Command + } + + tests := map[string]parameters{ + "Happy path xfs": { + publishInfo: mockVolumeInfo, + sharedTargetPath: "", + fsType: "xfs", + mountOptions: "", + err: nil, + getMountClient: func() Mount { + mockMountClient := mock_filesystem.NewMockMount(gomock.NewController(t)) + mockMountClient.EXPECT().MountFilesystemForResize(gomock.Any(), mockVolumeInfo.DevicePath, + "", "").Return("./", nil) + mockMountClient.EXPECT().RemoveMountPoint(gomock.Any(), "./").Return(nil) + return mockMountClient + }, + getCommandClient: func() execCmd.Command { + mockCommand := mock_exec.NewMockCommand(gomock.NewController(t)) + mockCommand.EXPECT().Execute(context.Background(), "xfs_growfs", "./").Return(nil, nil) + return mockCommand + }, + }, + "Happy path ext3": { + publishInfo: mockVolumeInfo, + sharedTargetPath: "", + fsType: "ext3", + mountOptions: "", + err: nil, + getMountClient: func() Mount { + mockMountClient := mock_filesystem.NewMockMount(gomock.NewController(t)) + mockMountClient.EXPECT().MountFilesystemForResize(gomock.Any(), mockVolumeInfo.DevicePath, + "", "").Return("./", nil) + mockMountClient.EXPECT().RemoveMountPoint(gomock.Any(), "./").Return(nil) + return mockMountClient + }, + getCommandClient: func() execCmd.Command { + mockCommand := mock_exec.NewMockCommand(gomock.NewController(t)) + mockCommand.EXPECT().Execute(context.Background(), "resize2fs", mockVolumeInfo.DevicePath).Return(nil, nil) + return mockCommand + }, + }, + "Unsupported filesystem type": { + publishInfo: mockVolumeInfo, + sharedTargetPath: "", + fsType: "fake-fs", + mountOptions: "", + err: fmt.Errorf(""), + getMountClient: func() Mount { + mockMountClient := mock_filesystem.NewMockMount(gomock.NewController(t)) + mockMountClient.EXPECT().MountFilesystemForResize(gomock.Any(), mockVolumeInfo.DevicePath, + "", "").Return("/mount/point", nil) + mockMountClient.EXPECT().RemoveMountPoint(gomock.Any(), "/mount/point").Return(nil) + return mockMountClient + }, + }, + "Mount Error": { + publishInfo: mockVolumeInfo, + sharedTargetPath: "", + fsType: "", + mountOptions: "", + err: fmt.Errorf(""), + getMountClient: func() Mount { + mockMountClient := mock_filesystem.NewMockMount(gomock.NewController(t)) + mockMountClient.EXPECT().MountFilesystemForResize(gomock.Any(), mockVolumeInfo.DevicePath, + "", "").Return("", fmt.Errorf("mount error")) + return mockMountClient + }, + }, + "Expand failed": { + publishInfo: mockVolumeInfo, + sharedTargetPath: "", + fsType: "ext3", + mountOptions: "", + err: fmt.Errorf(""), + getMountClient: func() Mount { + mockMountClient := mock_filesystem.NewMockMount(gomock.NewController(t)) + mockMountClient.EXPECT().MountFilesystemForResize(gomock.Any(), mockVolumeInfo.DevicePath, + "", "").Return("./", nil) + mockMountClient.EXPECT().RemoveMountPoint(gomock.Any(), "./").Return(nil) + return mockMountClient + }, + getCommandClient: func() execCmd.Command { + mockCommand := mock_exec.NewMockCommand(gomock.NewController(t)) + mockCommand.EXPECT().Execute(context.Background(), "resize2fs", + mockVolumeInfo.DevicePath).Return(nil, fmt.Errorf("resize error")) + return mockCommand + }, + }, + "Stat FS error": { + publishInfo: mockVolumeInfo, + sharedTargetPath: "", + fsType: "ext3", + mountOptions: "", + err: fmt.Errorf(""), + getMountClient: func() Mount { + mockMountClient := mock_filesystem.NewMockMount(gomock.NewController(t)) + mockMountClient.EXPECT().MountFilesystemForResize(gomock.Any(), mockVolumeInfo.DevicePath, + "", "").Return("", nil) + mockMountClient.EXPECT().RemoveMountPoint(gomock.Any(), "").Return(nil) + return mockMountClient + }, + }, + } + for name, params := range tests { + t.Run(name, func(t *testing.T) { + fsClient := NewDetailed(nil, nil, nil) + + if params.getMountClient != nil { + fsClient.mountClient = params.getMountClient() + } + + if params.getCommandClient != nil { + fsClient.command = params.getCommandClient() + } + + _, err := fsClient.ExpandFilesystemOnNode(context.Background(), params.publishInfo, + params.publishInfo.DevicePath, params.sharedTargetPath, params.fsType, params.mountOptions) + if params.err != nil { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/utils/filesystem/filesystem_test.go b/utils/filesystem/filesystem_test.go new file mode 100644 index 000000000..8b8e694fe --- /dev/null +++ b/utils/filesystem/filesystem_test.go @@ -0,0 +1,395 @@ +package filesystem + +import ( + "context" + "encoding/json" + "fmt" + "io/fs" + "testing" + "time" + + "github.com/spf13/afero" + "github.com/stretchr/testify/assert" + "go.uber.org/mock/gomock" + + "github.com/netapp/trident/mocks/mock_utils/mock_exec" + "github.com/netapp/trident/utils/errors" + execCmd "github.com/netapp/trident/utils/exec" + "github.com/netapp/trident/utils/models" +) + +// MockFs is a struct that embeds the afero in-memory filesystem, so that we can +// implement a version of Open that can return a permissions error. +type MockFs struct { + afero.MemMapFs +} + +func (m *MockFs) Open(name string) (afero.File, error) { + return nil, fs.ErrPermission +} + +func TestReadJSONFile_Succeeds(t *testing.T) { + osFs := afero.NewMemMapFs() + file, err := osFs.Create("foo.json") + assert.NoError(t, err) + + pubInfo := &models.VolumePublishInfo{} + pubInfo.NfsServerIP = "1.1.1.1" + pInfo, err := json.Marshal(pubInfo) + _, err = file.Write(pInfo) + assert.NoError(t, err) + + returnedPubInfo := &models.VolumePublishInfo{} + jsonRW := NewJSONReaderWriter(osFs) + err = jsonRW.ReadJSONFile(context.Background(), returnedPubInfo, "foo.json", "") + assert.NoError(t, err, "did not expect error reading valid JSON") + assert.Equal(t, pubInfo.NfsServerIP, returnedPubInfo.NfsServerIP, "expected read to unmarshal correctly with the"+ + " expected data") +} + +func TestReadJSONFile_FailsWithIncompleteJSON(t *testing.T) { + osFs := afero.NewMemMapFs() + file, err := osFs.Create("foo.json") + assert.NoError(t, err) + + // Will cause an unexpected EOF because the start of the file is the beginning token for valid JSON, but the ending + // token is never found. + _, err = file.Write([]byte("{")) + assert.NoError(t, err) + + returnedPubInfo := &models.VolumePublishInfo{} + jsonReaderWriter := NewJSONReaderWriter(osFs) + err = jsonReaderWriter.ReadJSONFile(context.Background(), returnedPubInfo, "foo.json", "") + assert.True(t, errors.IsInvalidJSONError(err), "expected invalidJSONError due to file containing incomplete JSON") +} + +func TestReadJSONFile_FailsBecauseUnmarshalTypeError(t *testing.T) { + osFs := afero.NewMemMapFs() + file, err := osFs.Create("foo.json") + assert.NoError(t, err) + + type BadPublishInfo struct { + HostIQN bool `json:"hostIQN,omitempty"` + } + + pubInfo := &BadPublishInfo{} + pubInfo.HostIQN = true + pInfo, err := json.Marshal(pubInfo) + _, err = file.Write(pInfo) + assert.NoError(t, err) + + returnedPubInfo := &models.VolumePublishInfo{} + jsonReaderWriter := NewJSONReaderWriter(osFs) + err = jsonReaderWriter.ReadJSONFile(context.Background(), returnedPubInfo, "foo.json", "") + assert.True(t, errors.IsInvalidJSONError(err), "expected invalidJSONError due to unmarshallable type in valid JSON") +} + +func TestReadJSONFile_FailsBecauseNoFile(t *testing.T) { + returnedPubInfo := &models.VolumePublishInfo{} + jsonReaderWriter := NewJSONReaderWriter(afero.NewMemMapFs()) + err := jsonReaderWriter.ReadJSONFile(context.Background(), returnedPubInfo, "foo.json", "") + assert.True(t, errors.IsNotFoundError(err), "expected NotFoundError when file doesn't exist") +} + +func TestReadJSONFile_FailsWithPermissionsError(t *testing.T) { + osFs := afero.NewMemMapFs() + osFs = &MockFs{} + _, err := osFs.Create("foo.json") + assert.NoError(t, err) + + returnedPubInfo := &models.VolumePublishInfo{} + jsonReaderWriter := NewJSONReaderWriter(osFs) + err = jsonReaderWriter.ReadJSONFile(context.Background(), returnedPubInfo, "foo.json", "") + assert.True(t, !errors.IsInvalidJSONError(err) && !errors.IsNotFoundError(err), "expected unwrapped error") +} + +func TestReadJSONFile_FailsWithSyntaxError(t *testing.T) { + osFs := afero.NewMemMapFs() + file, err := osFs.Create("foo.json") + assert.NoError(t, err) + // does not contain the beginning curly brace, so won't cause unexpected EOF + _, err = file.Write([]byte("garbage")) + assert.NoError(t, err) + + returnedPubInfo := &models.VolumePublishInfo{} + jsonReaderWriter := NewJSONReaderWriter(osFs) + err = jsonReaderWriter.ReadJSONFile(context.Background(), returnedPubInfo, "foo.json", "") + assert.True(t, errors.IsInvalidJSONError(err), "expected invalid JSON if syntax error") +} + +func TestReadJSONFile_FailsBecauseEmptyFile(t *testing.T) { + osFs := afero.NewMemMapFs() + _, err := osFs.Create("foo.json") + assert.NoError(t, err) + + returnedPubInfo := &models.VolumePublishInfo{} + jsonReaderWriter := NewJSONReaderWriter(osFs) + err = jsonReaderWriter.ReadJSONFile(context.Background(), returnedPubInfo, "foo.json", "") + assert.Error(t, err, "expected an error from an empty file") + assert.True(t, errors.IsInvalidJSONError(err), "expected InvalidJSONError due to empty file") +} + +func TestWriteJSONFile_Succeeds(t *testing.T) { + osFs := afero.NewMemMapFs() + + pubInfo := &models.VolumePublishInfo{} + pubInfo.NfsServerIP = "1.1.1.1" + + jsonReaderWriter := NewJSONReaderWriter(osFs) + err := jsonReaderWriter.WriteJSONFile(context.Background(), pubInfo, "foo.json", "") + assert.NoError(t, err, "did not expect error writing JSON file") + _, err = osFs.Stat("foo.json") + assert.NoError(t, err, "expected file to exist after writing it") + + contents, err := afero.ReadFile(osFs, "foo.json") + writtenPubInfo := &models.VolumePublishInfo{} + err = json.Unmarshal(contents, writtenPubInfo) + assert.NoError(t, err, "expected written file's contents to be JSON and unmarshallable") + assert.Equal(t, pubInfo.NfsServerIP, writtenPubInfo.NfsServerIP, "expected written field value to be present") +} + +func TestWriteJSONFile_FailsOnReadOnlyFs(t *testing.T) { + osFs := afero.NewReadOnlyFs(afero.NewMemMapFs()) + + pubInfo := &models.VolumePublishInfo{} + pubInfo.NfsServerIP = "1.1.1.1" + + jsonReaderWriter := NewJSONReaderWriter(osFs) + err := jsonReaderWriter.WriteJSONFile(context.Background(), pubInfo, "foo.json", "") + assert.Error(t, err, "expected error writing to read-only filesystem") +} + +func TestWriteJSONFile_FailsWritingNotMarshallableData(t *testing.T) { + osFs := afero.NewMemMapFs() + + pubInfo := make(chan int) + + jsonReaderWriter := NewJSONReaderWriter(osFs) + err := jsonReaderWriter.WriteJSONFile(context.Background(), &pubInfo, "foo.json", "") + assert.Error(t, err, "expected error trying to write something that can't be marshalled to JSON") +} + +func TestDeleteFile_Succeeds(t *testing.T) { + osFs := afero.NewMemMapFs() + fsClient := NewDetailed(execCmd.NewCommand(), osFs, nil) + _, err := osFs.Create("foo.json") + assert.NoError(t, err) + + _, err = fsClient.DeleteFile(context.Background(), "foo.json", "") + assert.NoError(t, err, "did not expect error deleting file") +} + +func TestDeleteFile_SucceedsWhenFileDoesntExist(t *testing.T) { + osFs := afero.NewMemMapFs() + fsClient := NewDetailed(execCmd.NewCommand(), osFs, nil) + + _, err := fsClient.DeleteFile(context.Background(), "foo.json", "") + assert.NoError(t, err, "expected no error deleting a file when the file doesn't exist") +} + +func TestDeleteFile_FailsOnReadOnlyFilesystem(t *testing.T) { + osFs := afero.NewMemMapFs() + + _, err := osFs.Create("foo.json") + assert.NoError(t, err) + + osFs = afero.NewReadOnlyFs(osFs) + + fsClient := NewDetailed(execCmd.NewCommand(), osFs, nil) + _, err = fsClient.DeleteFile(context.Background(), "foo.json", "") + assert.Error(t, err, "expected an error deleting a file on a read-only filesystem") +} + +func TestGetDFOutput(t *testing.T) { + mockDFOutput := []byte( + "Mounted on Filesystem\n" + + "/run tmpfs\n" + + "/ /dev/sda2\n", + ) + + type parameters struct { + name string + mockOutput []byte + mockError error + expectedResult []models.DFInfo + expectError bool + } + + tests := map[string]parameters{ + "Success": { + mockOutput: mockDFOutput, + mockError: nil, + expectedResult: []models.DFInfo{ + {Target: "/run", Source: "tmpfs"}, + {Target: "/", Source: "/dev/sda2"}, + }, + expectError: false, + }, + "EmptyOutput": { + mockOutput: []byte(""), + mockError: nil, + expectedResult: nil, + expectError: false, + }, + "Error": { + mockOutput: nil, + mockError: fmt.Errorf("error"), + expectedResult: nil, + expectError: true, + }, + } + + for name, params := range tests { + t.Run(name, func(t *testing.T) { + mockCtrl := gomock.NewController(t) + mockExec := mock_exec.NewMockCommand(mockCtrl) + fsClient := NewDetailed(mockExec, nil, nil) + + mockExec.EXPECT().Execute(gomock.Any(), "df", "--output=target,source").Return(params.mockOutput, params.mockError) + + result, err := fsClient.GetDFOutput(context.Background()) + if params.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + assert.Equal(t, params.expectedResult, result) + }) + } +} + +func TestFormatVolumeRetry(t *testing.T) { + originalMaxDuration := formatVolumeMaxRetryDuration + defer func() { formatVolumeMaxRetryDuration = originalMaxDuration }() + formatVolumeMaxRetryDuration = 3 * time.Second + + type parameters struct { + device string + fstype string + options string + getMockCmd func() *mock_exec.MockCommand + expectError bool + } + tests := map[string]parameters{ + "Success with xfs": { + device: "/dev/sda1", + fstype: Xfs, + options: "", + getMockCmd: func() *mock_exec.MockCommand { + mockCmd := mock_exec.NewMockCommand(gomock.NewController(t)) + mockCmd.EXPECT().Execute(gomock.Any(), "mkfs.xfs", "-f", "/dev/sda1").Return(nil, nil).Times(1) + return mockCmd + }, + expectError: false, + }, + "Success with ext3": { + device: "/dev/sda1", + fstype: Ext3, + options: "", + getMockCmd: func() *mock_exec.MockCommand { + mockCmd := mock_exec.NewMockCommand(gomock.NewController(t)) + mockCmd.EXPECT().Execute(gomock.Any(), "mkfs.ext3", "-F", "/dev/sda1").Return(nil, nil).Times(1) + return mockCmd + }, + expectError: false, + }, + "Success with ext4": { + device: "/dev/sda1", + fstype: Ext4, + options: "", + getMockCmd: func() *mock_exec.MockCommand { + mockCmd := mock_exec.NewMockCommand(gomock.NewController(t)) + mockCmd.EXPECT().Execute(gomock.Any(), "mkfs.ext4", "-F", "/dev/sda1").Return(nil, nil).Times(1) + return mockCmd + }, + expectError: false, + }, + "Unsupported filesystem": { + device: "/dev/sda1", + fstype: "unsupported", + options: "", + getMockCmd: nil, + expectError: true, + }, + "Error formatting ext4": { + device: "/dev/sda1", + fstype: Ext4, + options: "", + getMockCmd: func() *mock_exec.MockCommand { + mockCmd := mock_exec.NewMockCommand(gomock.NewController(t)) + mockCmd.EXPECT().Execute(gomock.Any(), "mkfs.ext4", "-F", "/dev/sda1").Return([]byte("error"), + errors.New("mock error")).AnyTimes() + return mockCmd + }, + expectError: true, + }, + } + + for name, params := range tests { + t.Run(name, func(t *testing.T) { + var mockCmd *mock_exec.MockCommand + if params.getMockCmd != nil { + mockCmd = params.getMockCmd() + } + fsClient := NewDetailed(mockCmd, afero.NewMemMapFs(), nil) + err := fsClient.formatVolumeRetry(context.Background(), params.device, params.fstype, params.options) + if params.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestRepairVolume(t *testing.T) { + mockCtrl := gomock.NewController(t) + mockExec := mock_exec.NewMockCommand(mockCtrl) + fsClient := NewDetailed(mockExec, nil, nil) + + type parameters struct { + device string + fstype string + mockSetup func() + } + tests := map[string]parameters{ + "fsck.xfs does nothing": { + device: "/dev/sda1", + fstype: Xfs, + mockSetup: func() {}, + }, + "Success with ext3": { + device: "/dev/sda1", + fstype: Ext3, + mockSetup: func() { + mockExec.EXPECT().Execute(gomock.Any(), "fsck.ext3", "-p", "/dev/sda1").Return(nil, nil).Times(1) + }, + }, + "Success with ext4": { + device: "/dev/sda1", + fstype: Ext4, + mockSetup: func() { + mockExec.EXPECT().Execute(gomock.Any(), "fsck.ext4", "-p", "/dev/sda1").Return(nil, nil).Times(1) + }, + }, + "Unsupported filesystem": { + device: "/dev/sda1", + fstype: "unsupported", + mockSetup: func() {}, + }, + "Error executing fsck": { + device: "/dev/sda1", + fstype: Ext4, + mockSetup: func() { + mockExec.EXPECT().Execute(gomock.Any(), "fsck.ext4", "-p", "/dev/sda1").Return(nil, fmt.Errorf("mock error")).Times(1) + }, + }, + } + + for name, params := range tests { + t.Run(name, func(t *testing.T) { + params.mockSetup() + fsClient.RepairVolume(context.Background(), params.device, params.fstype) + }) + } +} diff --git a/utils/filesystem_windows.go b/utils/filesystem/filesystem_windows.go similarity index 58% rename from utils/filesystem_windows.go rename to utils/filesystem/filesystem_windows.go index 398e35353..1107cd482 100644 --- a/utils/filesystem_windows.go +++ b/utils/filesystem/filesystem_windows.go @@ -1,6 +1,6 @@ // Copyright 2022 NetApp, Inc. All Rights Reserved. -package utils +package filesystem import ( "os" @@ -9,21 +9,23 @@ import ( "golang.org/x/net/context" . "github.com/netapp/trident/logging" + "github.com/netapp/trident/utils/csiutils" "github.com/netapp/trident/utils/errors" "github.com/netapp/trident/utils/models" ) -func getFilesystemSize(ctx context.Context, _ string) (int64, error) { +func (f *FSClient) getFilesystemSize(ctx context.Context, _ string) (int64, error) { Logc(ctx).Debug(">>>> filesystem_windows.getFilesystemSize") defer Logc(ctx).Debug("<<<< filesystem_windows.getFilesystemSize") return 0, errors.UnsupportedError("getFilesystemSize is not supported for windows") } -func GetFilesystemStats(ctx context.Context, path string) (int64, int64, int64, int64, int64, int64, error) { - Logc(ctx).Debug(">>>> mount_windows.GetFilesystemStats") - defer Logc(ctx).Debug("<<<< mount_windows.GetFilesystemStats") +func (f *FSClient) GetFilesystemStats(ctx context.Context, path string) (int64, int64, int64, int64, int64, int64, + error) { + Logc(ctx).Debug(">>>> filesystem_windows.GetFilesystemStats") + defer Logc(ctx).Debug("<<<< filesystem_windows.GetFilesystemStats") - csiproxy, err := NewCSIProxyUtils() + csiproxy, err := csiutils.NewCSIProxyUtils() if err != nil { Logc(ctx).Errorf("Failed to instantiate CSI proxy clients. Error: %v", err) } @@ -53,10 +55,18 @@ func GetDeviceFilePath(ctx context.Context, _, volumeId string) (string, error) } // GetUnmountPath returns unmount path for volume -func GetUnmountPath(ctx context.Context, trackingInfo *models.VolumeTrackingInfo) (string, error) { - Logc(ctx).Debug(">>>> osutils_windows.GetUnmountPath") - defer Logc(ctx).Debug("<<<< osutils_windows.GetUnmountPath") +func (f *FSClient) GetUnmountPath(ctx context.Context, trackingInfo *models.VolumeTrackingInfo) (string, error) { + Logc(ctx).Debug(">>>> filesystem_windows.GetUnmountPath") + defer Logc(ctx).Debug("<<<< filesystem_windows.GetUnmountPath") path := "\\" + trackingInfo.SMBServer + trackingInfo.SMBPath return strings.Replace(path, "/", "\\", -1), nil } + +func (f *FSClient) GenerateAnonymousMemFile(tempFileName, content string) (int, error) { + ctx := context.Background() + Logc(ctx).Debug(">>>> filesystem_windows.generateAnonymousMemFile") + defer Logc(ctx).Debug("<<<< filesystem_windows.generateAnonymousMemFile") + + return 0, errors.UnsupportedError("generateAnonymousMemFile is not supported for windows") +} diff --git a/utils/filesystem_windows_test.go b/utils/filesystem/filesystem_windows_test.go similarity index 53% rename from utils/filesystem_windows_test.go rename to utils/filesystem/filesystem_windows_test.go index 4a7133262..9b66e5a21 100644 --- a/utils/filesystem_windows_test.go +++ b/utils/filesystem/filesystem_windows_test.go @@ -1,6 +1,6 @@ // Copyright 2022 NetApp, Inc. All Rights Reserved. -package utils +package filesystem import ( "context" @@ -13,9 +13,19 @@ import ( func TestGetFilesystemSize(t *testing.T) { ctx := context.Background() + fsClient := New(nil) - result, err := getFilesystemSize(ctx, "") + result, err := fsClient.getFilesystemSize(ctx, "") assert.Equal(t, result, int64(0), "got non-zero filesystem size") assert.Error(t, err, "no error") assert.True(t, errors.IsUnsupportedError(err), "not UnsupportedError") } + +func TestGenerateAnonymousMemFile(t *testing.T) { + fsClient := New(nil) + + result, err := fsClient.GenerateAnonymousMemFile("", "") + assert.Equal(t, 0, result) + assert.Error(t, err, "no error") + assert.True(t, errors.IsUnsupportedError(err), "not UnsupportedError") +} diff --git a/utils/filesystem/json.go b/utils/filesystem/json.go new file mode 100644 index 000000000..f35795de2 --- /dev/null +++ b/utils/filesystem/json.go @@ -0,0 +1,95 @@ +// Copyright 2022 NetApp, Inc. All Rights Reserved. + +package filesystem + +//go:generate mockgen -destination=../../mocks/mock_utils/mock_filesystem/mock_json_utils.go github.com/netapp/trident/utils/filesystem JSONReaderWriter + +import ( + "context" + "encoding/json" + "fmt" + "io/fs" + "os" + + "github.com/spf13/afero" + + . "github.com/netapp/trident/logging" + "github.com/netapp/trident/utils/errors" +) + +type JSONReaderWriter interface { + WriteJSONFile(ctx context.Context, fileContents interface{}, filepath, fileDescription string) error + ReadJSONFile(ctx context.Context, fileContents interface{}, filepath, fileDescription string) error +} + +type jsonReaderWriter struct { + osFs afero.Fs +} + +func NewJSONReaderWriter(osFs afero.Fs) JSONReaderWriter { + return &jsonReaderWriter{ + osFs: osFs, + } +} + +// WriteJSONFile writes the contents of any type of struct to a file, with logging. +func (j jsonReaderWriter) WriteJSONFile( + ctx context.Context, fileContents interface{}, filepath, fileDescription string, +) error { + file, err := j.osFs.OpenFile(filepath, os.O_WRONLY|os.O_CREATE, 0o600) + if err != nil { + return err + } + defer func() { _ = file.Close() }() + + if err = json.NewEncoder(file).Encode(fileContents); err != nil { + Logc(ctx).WithFields(LogFields{ + "filename": filepath, + "error": err.Error(), + }).Error(fmt.Sprintf("Unable to write %s file.", fileDescription)) + return err + } + + return nil +} + +// ReadJSONFile reads a file at the specified path and deserializes its contents into the provided fileContents var. +// fileContents must be a pointer to a struct, not a pointer type! +func (j *jsonReaderWriter) ReadJSONFile( + ctx context.Context, fileContents interface{}, filepath, fileDescription string, +) error { + file, err := j.osFs.Open(filepath) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + Logc(ctx).WithFields(LogFields{ + "filepath": filepath, + "error": err.Error(), + }).Warningf("Could not find JSON file: %s.", filepath) + return errors.NotFoundError(err.Error()) + } + return err + } + defer func() { _ = file.Close() }() + + fileInfo, err := file.Stat() + if err != nil { + return err + } + // We do not consider an empty file valid JSON. + if fileInfo.Size() == 0 { + return errors.InvalidJSONError("file was empty, which is not considered valid JSON") + } + + err = json.NewDecoder(file).Decode(fileContents) + if err != nil { + Logc(ctx).WithFields(LogFields{ + "filename": filepath, + "error": err.Error(), + }).Error(fmt.Sprintf("Could not parse %s file.", fileDescription)) + + e, _ := errors.AsInvalidJSONError(err) + return e + } + + return nil +} diff --git a/utils/filesystem/utils.go b/utils/filesystem/utils.go new file mode 100644 index 000000000..8adf5d120 --- /dev/null +++ b/utils/filesystem/utils.go @@ -0,0 +1,27 @@ +// Copyright 2024 NetApp, Inc. All Rights Reserved. + +package filesystem + +import ( + "fmt" + "strings" + + "golang.org/x/text/cases" + "golang.org/x/text/language" +) + +// Title minimally replaces the deprecated strings.Title() function. +func Title(str string) string { + return cases.Title(language.Und, cases.NoLower).String(str) +} + +// VerifyFilesystemSupport checks for a supported file system type +func VerifyFilesystemSupport(fs string) (string, error) { + fstype := strings.ToLower(fs) + switch fstype { + case Xfs, Ext3, Ext4, Raw: + return fstype, nil + default: + return "", fmt.Errorf("unsupported fileSystemType option: %s", fstype) + } +} diff --git a/utils/filesystem_linux_test.go b/utils/filesystem_linux_test.go deleted file mode 100644 index a715d8684..000000000 --- a/utils/filesystem_linux_test.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2022 NetApp, Inc. All Rights Reserved. - -package utils - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/netapp/trident/utils/errors" - "github.com/netapp/trident/utils/models" -) - -func TestGetDeviceFilePath(t *testing.T) { - ctx := context.Background() - - result, err := GetDeviceFilePath(ctx, "/test-path", "") - assert.Equal(t, result, "/test-path", "got device file path") - assert.NoError(t, err, "error") -} - -func TestGetUnmountPath(t *testing.T) { - ctx := context.Background() - - result, err := GetUnmountPath(ctx, &models.VolumeTrackingInfo{}) - assert.Equal(t, result, "", "got unmount path") - assert.Error(t, err, "no error") - assert.True(t, errors.IsUnsupportedError(err), "not UnsupportedError") -} diff --git a/utils/filesystem_test.go b/utils/filesystem_test.go deleted file mode 100644 index 77b02e9bf..000000000 --- a/utils/filesystem_test.go +++ /dev/null @@ -1,337 +0,0 @@ -package utils - -import ( - "context" - "encoding/json" - "fmt" - "testing" - - "github.com/spf13/afero" - "github.com/stretchr/testify/assert" - "go.uber.org/mock/gomock" - - "github.com/netapp/trident/mocks/mock_utils/mock_exec" - "github.com/netapp/trident/utils/errors" - execCmd "github.com/netapp/trident/utils/exec" - "github.com/netapp/trident/utils/models" -) - -func TestReadJSONFile_Succeeds(t *testing.T) { - defer func() { osFs = afero.NewOsFs() }() - osFs = afero.NewMemMapFs() - file, err := osFs.Create("foo.json") - assert.NoError(t, err) - - pubInfo := &models.VolumePublishInfo{} - pubInfo.NfsServerIP = "1.1.1.1" - pInfo, err := json.Marshal(pubInfo) - _, err = file.Write(pInfo) - assert.NoError(t, err) - - returnedPubInfo := &models.VolumePublishInfo{} - err = JsonReaderWriter.ReadJSONFile(context.Background(), returnedPubInfo, "foo.json", "") - assert.NoError(t, err, "did not expect error reading valid JSON") - assert.Equal(t, pubInfo.NfsServerIP, returnedPubInfo.NfsServerIP, "expected read to unmarshal correctly with the"+ - " expected data") -} - -func TestReadJSONFile_FailsWithIncompleteJSON(t *testing.T) { - defer func() { osFs = afero.NewOsFs() }() - osFs = afero.NewMemMapFs() - file, err := osFs.Create("foo.json") - assert.NoError(t, err) - - // Will cause an unexpected EOF because the start of the file is the beginning token for valid JSON, but the ending - // token is never found. - _, err = file.Write([]byte("{")) - assert.NoError(t, err) - - returnedPubInfo := &models.VolumePublishInfo{} - err = JsonReaderWriter.ReadJSONFile(context.Background(), returnedPubInfo, "foo.json", "") - assert.True(t, errors.IsInvalidJSONError(err), "expected invalidJSONError due to file containing incomplete JSON") -} - -func TestReadJSONFile_FailsBecauseUnmarshalTypeError(t *testing.T) { - defer func() { osFs = afero.NewOsFs() }() - osFs = afero.NewMemMapFs() - file, err := osFs.Create("foo.json") - assert.NoError(t, err) - - type BadPublishInfo struct { - HostIQN bool `json:"hostIQN,omitempty"` - } - - pubInfo := &BadPublishInfo{} - pubInfo.HostIQN = true - pInfo, err := json.Marshal(pubInfo) - _, err = file.Write(pInfo) - assert.NoError(t, err) - - returnedPubInfo := &models.VolumePublishInfo{} - err = JsonReaderWriter.ReadJSONFile(context.Background(), returnedPubInfo, "foo.json", "") - assert.True(t, errors.IsInvalidJSONError(err), "expected invalidJSONError due to unmarshallable type in valid JSON") -} - -func TestReadJSONFile_FailsBecauseNoFile(t *testing.T) { - returnedPubInfo := &models.VolumePublishInfo{} - err := JsonReaderWriter.ReadJSONFile(context.Background(), returnedPubInfo, "foo.json", "") - assert.True(t, errors.IsNotFoundError(err), "expected NotFoundError when file doesn't exist") -} - -func TestReadJSONFile_FailsWithPermissionsError(t *testing.T) { - defer func() { osFs = afero.NewOsFs() }() - osFs = &MockFs{} - _, err := osFs.Create("foo.json") - assert.NoError(t, err) - - returnedPubInfo := &models.VolumePublishInfo{} - err = JsonReaderWriter.ReadJSONFile(context.Background(), returnedPubInfo, "foo.json", "") - assert.True(t, !errors.IsInvalidJSONError(err) && !errors.IsNotFoundError(err), "expected unwrapped error") -} - -func TestReadJSONFile_FailsWithSyntaxError(t *testing.T) { - defer func() { osFs = afero.NewOsFs() }() - osFs = afero.NewMemMapFs() - file, err := osFs.Create("foo.json") - assert.NoError(t, err) - // does not contain the beginning curly brace, so won't cause unexpected EOF - _, err = file.Write([]byte("garbage")) - assert.NoError(t, err) - - returnedPubInfo := &models.VolumePublishInfo{} - err = JsonReaderWriter.ReadJSONFile(context.Background(), returnedPubInfo, "foo.json", "") - assert.True(t, errors.IsInvalidJSONError(err), "expected invalid JSON if syntax error") -} - -func TestReadJSONFile_FailsBecauseEmptyFile(t *testing.T) { - defer func() { osFs = afero.NewOsFs() }() - osFs = afero.NewMemMapFs() - _, err := osFs.Create("foo.json") - assert.NoError(t, err) - - returnedPubInfo := &models.VolumePublishInfo{} - err = JsonReaderWriter.ReadJSONFile(context.Background(), returnedPubInfo, "foo.json", "") - assert.Error(t, err, "expected an error from an empty file") - assert.True(t, errors.IsInvalidJSONError(err), "expected InvalidJSONError due to empty file") -} - -func TestWriteJSONFile_Succeeds(t *testing.T) { - defer func() { osFs = afero.NewOsFs() }() - osFs = afero.NewMemMapFs() - - pubInfo := &models.VolumePublishInfo{} - pubInfo.NfsServerIP = "1.1.1.1" - - err := JsonReaderWriter.WriteJSONFile(context.Background(), pubInfo, "foo.json", "") - assert.NoError(t, err, "did not expect error writing JSON file") - _, err = osFs.Stat("foo.json") - assert.NoError(t, err, "expected file to exist after writing it") - - contents, err := afero.ReadFile(osFs, "foo.json") - writtenPubInfo := &models.VolumePublishInfo{} - err = json.Unmarshal(contents, writtenPubInfo) - assert.NoError(t, err, "expected written file's contents to be JSON and unmarshallable") - assert.Equal(t, pubInfo.NfsServerIP, writtenPubInfo.NfsServerIP, "expected written field value to be present") -} - -func TestWriteJSONFile_FailsOnReadOnlyFs(t *testing.T) { - defer func() { osFs = afero.NewOsFs() }() - osFs = afero.NewReadOnlyFs(afero.NewMemMapFs()) - - pubInfo := &models.VolumePublishInfo{} - pubInfo.NfsServerIP = "1.1.1.1" - - err := JsonReaderWriter.WriteJSONFile(context.Background(), pubInfo, "foo.json", "") - assert.Error(t, err, "expected error writing to read-only filesystem") -} - -func TestWriteJSONFile_FailsWritingNotMarshallableData(t *testing.T) { - defer func() { osFs = afero.NewOsFs() }() - osFs = afero.NewMemMapFs() - - pubInfo := make(chan int) - - err := JsonReaderWriter.WriteJSONFile(context.Background(), &pubInfo, "foo.json", "") - assert.Error(t, err, "expected error trying to write something that can't be marshalled to JSON") -} - -func TestDeleteFile_Succeeds(t *testing.T) { - defer func() { osFs = afero.NewOsFs() }() - osFs = afero.NewMemMapFs() - _, err := osFs.Create("foo.json") - assert.NoError(t, err) - - _, err = DeleteFile(context.Background(), "foo.json", "") - assert.NoError(t, err, "did not expect error deleting file") -} - -func TestDeleteFile_SucceedsWhenFileDoesntExist(t *testing.T) { - defer func() { osFs = afero.NewOsFs() }() - osFs = afero.NewMemMapFs() - - _, err := DeleteFile(context.Background(), "foo.json", "") - assert.NoError(t, err, "expected no error deleting a file when the file doesn't exist") -} - -func TestDeleteFile_FailsOnReadOnlyFilesystem(t *testing.T) { - defer func() { osFs = afero.NewOsFs() }() - osFs = afero.NewMemMapFs() - _, err := osFs.Create("foo.json") - assert.NoError(t, err) - - osFs = afero.NewReadOnlyFs(osFs) - - _, err = DeleteFile(context.Background(), "foo.json", "") - assert.Error(t, err, "expected an error deleting a file on a read-only filesystem") -} - -func TestFormatVolume(t *testing.T) { - ctx := ctx() - mockCtrl := gomock.NewController(t) - mockexec := mock_exec.NewMockCommand(mockCtrl) - - tempDevicePath := "/dev/dm-7" - tempFormatOptions := fmt.Sprintf("-F%s-b 4096%s-T stride=16", FormatOptionsSeparator, FormatOptionsSeparator) - tempFailureError := "failed" - - ext4Cmd := "mkfs.ext4" - ext3Cmd := "mkfs.ext3" - xfsCmd := "mkfs.xfs" - - defer func(previousCommand execCmd.Command) { - command = previousCommand - }(command) - - command = mockexec - - tests := []struct { - message string - device string - fstype string - formatOptions string - mockSetup func() - isError bool - stdErrOutput []byte - }{ - { - "ext4 / no error / no format options", - tempDevicePath, - fsExt4, - "", - func() { - mockexec.EXPECT().Execute(gomock.Any(), ext4Cmd, gomock.Any(), gomock.Any()).Return(nil, nil).Times(1) - }, - false, - []byte(nil), - }, - { - "ext4 / no error / format options", - tempDevicePath, - fsExt4, - tempFormatOptions, - func() { - mockexec.EXPECT().Execute(gomock.Any(), ext4Cmd, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).Times(1) - }, - false, - []byte(nil), - }, - { - "ext4 / error", - tempDevicePath, - fsExt4, - "", - func() { - mockexec.EXPECT().Execute(gomock.Any(), ext4Cmd, gomock.Any(), gomock.Any()).Return([]byte(tempFailureError), fmt.Errorf("exit status: 1")).Times(1) - }, - true, - []byte(fmt.Sprintf("error formatting device %v: %v", tempDevicePath, tempFailureError)), - }, - { - "ext3 / no error / no format options", - tempDevicePath, - fsExt3, - "", - func() { - mockexec.EXPECT().Execute(gomock.Any(), ext3Cmd, gomock.Any(), gomock.Any()).Return(nil, nil).Times(1) - }, - false, - []byte(nil), - }, - { - "ext3 / no error / format options", - tempDevicePath, - fsExt3, - tempFormatOptions, - func() { - mockexec.EXPECT().Execute(gomock.Any(), ext3Cmd, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).Times(1) - }, - false, - []byte(nil), - }, - { - "ext3 / error", - tempDevicePath, - fsExt3, - "", - func() { - mockexec.EXPECT().Execute(gomock.Any(), ext3Cmd, gomock.Any(), gomock.Any()).Return([]byte(tempFailureError), fmt.Errorf("exit status: 1")).Times(1) - }, - true, - []byte(fmt.Sprintf("error formatting device %v: %v", tempDevicePath, tempFailureError)), - }, - { - "xfs / no error / no format options", - tempDevicePath, - fsXfs, - "", - func() { - mockexec.EXPECT().Execute(gomock.Any(), xfsCmd, gomock.Any(), gomock.Any()).Return(nil, nil).Times(1) - }, - false, - []byte(nil), - }, - { - "xfs / no error / format options", - tempDevicePath, - fsXfs, - tempFormatOptions, - func() { - mockexec.EXPECT().Execute(gomock.Any(), xfsCmd, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).Times(1) - }, - false, - []byte(nil), - }, - { - "xfs / error", - tempDevicePath, - fsXfs, - "", - func() { - mockexec.EXPECT().Execute(gomock.Any(), xfsCmd, gomock.Any(), gomock.Any()).Return([]byte(tempFailureError), fmt.Errorf("exit status: 1")).Times(1) - }, - true, - []byte(fmt.Sprintf("error formatting device %v: %v", tempDevicePath, tempFailureError)), - }, - { - "unsupported fs / error", - tempDevicePath, - "unsupported", - "", - func() {}, - true, - []byte(fmt.Sprintf("unsupported file system type: %s", "unsupported")), - }, - } - - for _, tt := range tests { - t.Run(tt.message, func(t *testing.T) { - tt.mockSetup() - err := formatVolume(ctx, tt.device, tt.fstype, tt.formatOptions) - if !tt.isError { - assert.NoError(t, err) - } else { - assert.Error(t, err) - } - }) - } -} diff --git a/utils/iscsi.go b/utils/iscsi.go index d718b928b..82a3e3b66 100644 --- a/utils/iscsi.go +++ b/utils/iscsi.go @@ -20,6 +20,7 @@ import ( "github.com/netapp/trident/internal/fiji" . "github.com/netapp/trident/logging" "github.com/netapp/trident/utils/errors" + "github.com/netapp/trident/utils/filesystem" "github.com/netapp/trident/utils/iscsi" "github.com/netapp/trident/utils/models" "github.com/netapp/trident/utils/mount" @@ -39,7 +40,7 @@ var ( mountClient, _ = mount.New() IscsiUtils = iscsi.NewReconcileUtils(chrootPathPrefix, NewOSClient()) iscsiClient = iscsi.NewDetailed(chrootPathPrefix, command, iscsi.DefaultSelfHealingExclusion, NewOSClient(), - NewDevicesClient(), NewFilesystemClient(), mountClient, IscsiUtils, afero.Afero{Fs: afero.NewOsFs()}) + NewDevicesClient(), filesystem.New(mountClient), mountClient, IscsiUtils, afero.Afero{Fs: afero.NewOsFs()}) // Non-persistent map to maintain flush delays/errors if any, for device path(s). iSCSIVolumeFlushExceptions = make(map[string]time.Time) diff --git a/utils/iscsi/iscsi.go b/utils/iscsi/iscsi.go index 2653bc706..e9186b2b2 100644 --- a/utils/iscsi/iscsi.go +++ b/utils/iscsi/iscsi.go @@ -5,7 +5,6 @@ package iscsi //go:generate mockgen -destination=../../mocks/mock_utils/mock_iscsi/mock_iscsi_client.go github.com/netapp/trident/utils/iscsi ISCSI //go:generate mockgen -destination=../../mocks/mock_utils/mock_iscsi/mock_iscsi_os_client.go github.com/netapp/trident/utils/iscsi OS //go:generate mockgen -destination=../../mocks/mock_utils/mock_iscsi/mock_iscsi_devices_client.go github.com/netapp/trident/utils/iscsi Devices -//go:generate mockgen -destination=../../mocks/mock_utils/mock_iscsi/mock_iscsi_filesystem_client.go github.com/netapp/trident/utils/iscsi FileSystem import ( "context" @@ -23,11 +22,11 @@ import ( "github.com/cenkalti/backoff/v4" "github.com/spf13/afero" - "github.com/netapp/trident/config" "github.com/netapp/trident/internal/fiji" . "github.com/netapp/trident/logging" "github.com/netapp/trident/utils/errors" tridentexec "github.com/netapp/trident/utils/exec" + "github.com/netapp/trident/utils/filesystem" "github.com/netapp/trident/utils/models" "github.com/netapp/trident/utils/mount" ) @@ -132,24 +131,19 @@ type Devices interface { NewLUKSDeviceFromMappingPath(ctx context.Context, mappingPath, volumeId string) (models.LUKSDeviceInterface, error) } -type FileSystem interface { - FormatVolume(ctx context.Context, device, fstype, formatOptions string) error - RepairVolume(ctx context.Context, device, fstype string) -} - type Client struct { chrootPathPrefix string command tridentexec.Command selfHealingExclusion []string osClient OS deviceClient Devices - fileSystemClient FileSystem + fileSystemClient filesystem.Filesystem mountClient mount.Mount iscsiUtils IscsiReconcileUtils os afero.Afero } -func New(osClient OS, deviceClient Devices, fileSystemClient FileSystem) (*Client, error) { +func New(osClient OS, deviceClient Devices) (*Client, error) { chrootPathPrefix := "" if os.Getenv("DOCKER_PLUGIN_MODE") != "" { chrootPathPrefix = "/host" @@ -162,12 +156,15 @@ func New(osClient OS, deviceClient Devices, fileSystemClient FileSystem) (*Clien return nil, fmt.Errorf("error creating mount client: %v", err) } + fsClient := filesystem.New(mountClient) + return NewDetailed(chrootPathPrefix, tridentexec.NewCommand(), DefaultSelfHealingExclusion, osClient, - deviceClient, fileSystemClient, mountClient, reconcileutils, osUtils), nil + deviceClient, fsClient, mountClient, reconcileutils, osUtils), nil } func NewDetailed(chrootPathPrefix string, command tridentexec.Command, selfHealingExclusion []string, osClient OS, - deviceClient Devices, fileSystemClient FileSystem, mountClient mount.Mount, iscsiUtils IscsiReconcileUtils, + deviceClient Devices, fileSystemClient filesystem.Filesystem, mountClient mount.Mount, + iscsiUtils IscsiReconcileUtils, os afero.Afero, ) *Client { return &Client{ @@ -410,7 +407,7 @@ func (client *Client) AttachVolume( devicePath = luksDevice.MappedDevicePath() } - if publishInfo.FilesystemType == config.FsRaw { + if publishInfo.FilesystemType == filesystem.Raw { return mpathSize, nil } diff --git a/utils/iscsi/iscsi_test.go b/utils/iscsi/iscsi_test.go index ec8373860..433c1aa3e 100644 --- a/utils/iscsi/iscsi_test.go +++ b/utils/iscsi/iscsi_test.go @@ -15,13 +15,14 @@ import ( "github.com/stretchr/testify/assert" "go.uber.org/mock/gomock" - "github.com/netapp/trident/config" mockexec "github.com/netapp/trident/mocks/mock_utils/mock_exec" + "github.com/netapp/trident/mocks/mock_utils/mock_filesystem" "github.com/netapp/trident/mocks/mock_utils/mock_iscsi" "github.com/netapp/trident/mocks/mock_utils/mock_models/mock_luks" "github.com/netapp/trident/mocks/mock_utils/mock_mount" "github.com/netapp/trident/utils/errors" tridentexec "github.com/netapp/trident/utils/exec" + "github.com/netapp/trident/utils/filesystem" "github.com/netapp/trident/utils/models" "github.com/netapp/trident/utils/mount" ) @@ -30,7 +31,6 @@ func TestNew(t *testing.T) { ctrl := gomock.NewController(t) osClient := mock_iscsi.NewMockOS(ctrl) devicesClient := mock_iscsi.NewMockDevices(ctrl) - FileSystemClient := mock_iscsi.NewMockFileSystem(ctrl) type parameters struct { setUpEnvironment func() @@ -50,7 +50,7 @@ func TestNew(t *testing.T) { params.setUpEnvironment() } - iscsiClient, err := New(osClient, devicesClient, FileSystemClient) + iscsiClient, err := New(osClient, devicesClient) assert.NoError(t, err) assert.NotNil(t, iscsiClient) }) @@ -62,7 +62,7 @@ func TestNewDetailed(t *testing.T) { ctrl := gomock.NewController(t) osClient := mock_iscsi.NewMockOS(ctrl) devicesClient := mock_iscsi.NewMockDevices(ctrl) - FileSystemClient := mock_iscsi.NewMockFileSystem(ctrl) + FileSystemClient := mock_filesystem.NewMockFilesystem(ctrl) mountClient := mock_mount.NewMockMount(ctrl) command := mockexec.NewMockCommand(ctrl) iscsiClient := NewDetailed(chrootPathPrefix, command, DefaultSelfHealingExclusion, osClient, devicesClient, FileSystemClient, @@ -76,7 +76,7 @@ func TestClient_AttachVolumeRetry(t *testing.T) { getCommand func(controller *gomock.Controller) tridentexec.Command getOSClient func(controller *gomock.Controller) OS getDeviceClient func(controller *gomock.Controller) Devices - getFileSystemClient func(controller *gomock.Controller) FileSystem + getFileSystemClient func(controller *gomock.Controller) filesystem.Filesystem getMountClient func(controller *gomock.Controller) mount.Mount getReconcileUtils func(controller *gomock.Controller) IscsiReconcileUtils getFileSystemUtils func() afero.Fs @@ -140,11 +140,11 @@ tcp: [4] 127.0.0.1:3260,1029 iqn.2016-04.com.open-iscsi:ef9f41e2ffa7:vs.3 (non-f mockDevices.EXPECT().GetISCSIDiskSize(context.TODO(), "/dev/sda").Return(int64(0), nil) mockDevices.EXPECT().GetISCSIDiskSize(context.TODO(), "/dev/dm-0").Return(int64(0), nil) mockDevices.EXPECT().WaitForDevice(context.TODO(), "/dev/dm-0").Return(nil) - mockDevices.EXPECT().GetDeviceFSType(context.TODO(), "/dev/dm-0").Return(config.FsExt4, nil) + mockDevices.EXPECT().GetDeviceFSType(context.TODO(), "/dev/dm-0").Return(filesystem.Ext4, nil) return mockDevices }, - getFileSystemClient: func(controller *gomock.Controller) FileSystem { - mockFileSystem := mock_iscsi.NewMockFileSystem(controller) + getFileSystemClient: func(controller *gomock.Controller) filesystem.Filesystem { + mockFileSystem := mock_filesystem.NewMockFilesystem(controller) return mockFileSystem }, getMountClient: func(controller *gomock.Controller) mount.Mount { @@ -169,7 +169,7 @@ tcp: [4] 127.0.0.1:3260,1029 iqn.2016-04.com.open-iscsi:ef9f41e2ffa7:vs.3 (non-f return fs }, publishInfo: models.VolumePublishInfo{ - FilesystemType: config.FsExt4, + FilesystemType: filesystem.Ext4, VolumeAccessInfo: models.VolumeAccessInfo{ IscsiAccessInfo: models.IscsiAccessInfo{ IscsiTargetPortal: "127.0.0.1", @@ -196,8 +196,8 @@ tcp: [4] 127.0.0.1:3260,1029 iqn.2016-04.com.open-iscsi:ef9f41e2ffa7:vs.3 (non-f mockDevices := mock_iscsi.NewMockDevices(controller) return mockDevices }, - getFileSystemClient: func(controller *gomock.Controller) FileSystem { - mockFileSystem := mock_iscsi.NewMockFileSystem(controller) + getFileSystemClient: func(controller *gomock.Controller) filesystem.Filesystem { + mockFileSystem := mock_filesystem.NewMockFilesystem(controller) return mockFileSystem }, getMountClient: func(controller *gomock.Controller) mount.Mount { @@ -234,8 +234,8 @@ tcp: [4] 127.0.0.1:3260,1029 iqn.2016-04.com.open-iscsi:ef9f41e2ffa7:vs.3 (non-f mockDevices := mock_iscsi.NewMockDevices(controller) return mockDevices }, - getFileSystemClient: func(controller *gomock.Controller) FileSystem { - mockFileSystem := mock_iscsi.NewMockFileSystem(controller) + getFileSystemClient: func(controller *gomock.Controller) filesystem.Filesystem { + mockFileSystem := mock_filesystem.NewMockFilesystem(controller) return mockFileSystem }, getMountClient: func(controller *gomock.Controller) mount.Mount { @@ -278,7 +278,7 @@ func TestClient_AttachVolume(t *testing.T) { getCommand func(controller *gomock.Controller) tridentexec.Command getOSClient func(controller *gomock.Controller) OS getDeviceClient func(controller *gomock.Controller) Devices - getFileSystemClient func(controller *gomock.Controller) FileSystem + getFileSystemClient func(controller *gomock.Controller) filesystem.Filesystem getMountClient func(controller *gomock.Controller) mount.Mount getReconcileUtils func(controller *gomock.Controller) IscsiReconcileUtils getFileSystemUtils func() afero.Fs @@ -322,8 +322,8 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` mockDevices := mock_iscsi.NewMockDevices(controller) return mockDevices }, - getFileSystemClient: func(controller *gomock.Controller) FileSystem { - mockFileSystem := mock_iscsi.NewMockFileSystem(controller) + getFileSystemClient: func(controller *gomock.Controller) filesystem.Filesystem { + mockFileSystem := mock_filesystem.NewMockFilesystem(controller) return mockFileSystem }, getMountClient: func(controller *gomock.Controller) mount.Mount { @@ -338,7 +338,7 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` return afero.NewMemMapFs() }, publishInfo: models.VolumePublishInfo{ - FilesystemType: config.FsExt4, + FilesystemType: filesystem.Ext4, }, volumeName: "test-volume", volumeMountPoint: "/mnt/test-volume", @@ -364,8 +364,8 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` mockDevices := mock_iscsi.NewMockDevices(controller) return mockDevices }, - getFileSystemClient: func(controller *gomock.Controller) FileSystem { - mockFileSystem := mock_iscsi.NewMockFileSystem(controller) + getFileSystemClient: func(controller *gomock.Controller) filesystem.Filesystem { + mockFileSystem := mock_filesystem.NewMockFilesystem(controller) return mockFileSystem }, getMountClient: func(controller *gomock.Controller) mount.Mount { @@ -380,7 +380,7 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` return afero.NewMemMapFs() }, publishInfo: models.VolumePublishInfo{ - FilesystemType: config.FsExt4, + FilesystemType: filesystem.Ext4, }, volumeName: "test-volume", volumeMountPoint: "/mnt/test-volume", @@ -408,8 +408,8 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` mockDevices := mock_iscsi.NewMockDevices(controller) return mockDevices }, - getFileSystemClient: func(controller *gomock.Controller) FileSystem { - mockFileSystem := mock_iscsi.NewMockFileSystem(controller) + getFileSystemClient: func(controller *gomock.Controller) filesystem.Filesystem { + mockFileSystem := mock_filesystem.NewMockFilesystem(controller) return mockFileSystem }, getMountClient: func(controller *gomock.Controller) mount.Mount { @@ -424,7 +424,7 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` return afero.NewMemMapFs() }, publishInfo: models.VolumePublishInfo{ - FilesystemType: config.FsExt4, + FilesystemType: filesystem.Ext4, }, volumeName: "test-volume", volumeMountPoint: "/mnt/test-volume", @@ -452,8 +452,8 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` mockDevices := mock_iscsi.NewMockDevices(controller) return mockDevices }, - getFileSystemClient: func(controller *gomock.Controller) FileSystem { - mockFileSystem := mock_iscsi.NewMockFileSystem(controller) + getFileSystemClient: func(controller *gomock.Controller) filesystem.Filesystem { + mockFileSystem := mock_filesystem.NewMockFilesystem(controller) return mockFileSystem }, getMountClient: func(controller *gomock.Controller) mount.Mount { @@ -468,7 +468,7 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` return afero.NewMemMapFs() }, publishInfo: models.VolumePublishInfo{ - FilesystemType: config.FsExt4, + FilesystemType: filesystem.Ext4, }, volumeName: "test-volume", volumeMountPoint: "/mnt/test-volume", @@ -497,8 +497,8 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` mockDevices := mock_iscsi.NewMockDevices(controller) return mockDevices }, - getFileSystemClient: func(controller *gomock.Controller) FileSystem { - mockFileSystem := mock_iscsi.NewMockFileSystem(controller) + getFileSystemClient: func(controller *gomock.Controller) filesystem.Filesystem { + mockFileSystem := mock_filesystem.NewMockFilesystem(controller) return mockFileSystem }, getMountClient: func(controller *gomock.Controller) mount.Mount { @@ -513,7 +513,7 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` return afero.NewMemMapFs() }, publishInfo: models.VolumePublishInfo{ - FilesystemType: config.FsExt4, + FilesystemType: filesystem.Ext4, }, volumeName: "test-volume", volumeMountPoint: "/mnt/test-volume", @@ -548,8 +548,8 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` mockDevices := mock_iscsi.NewMockDevices(controller) return mockDevices }, - getFileSystemClient: func(controller *gomock.Controller) FileSystem { - mockFileSystem := mock_iscsi.NewMockFileSystem(controller) + getFileSystemClient: func(controller *gomock.Controller) filesystem.Filesystem { + mockFileSystem := mock_filesystem.NewMockFilesystem(controller) return mockFileSystem }, getMountClient: func(controller *gomock.Controller) mount.Mount { @@ -576,7 +576,7 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` return fs }, publishInfo: models.VolumePublishInfo{ - FilesystemType: config.FsExt4, + FilesystemType: filesystem.Ext4, VolumeAccessInfo: models.VolumeAccessInfo{ IscsiAccessInfo: models.IscsiAccessInfo{ IscsiTargetIQN: targetIQN, @@ -610,8 +610,8 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` mockDevices := mock_iscsi.NewMockDevices(controller) return mockDevices }, - getFileSystemClient: func(controller *gomock.Controller) FileSystem { - mockFileSystem := mock_iscsi.NewMockFileSystem(controller) + getFileSystemClient: func(controller *gomock.Controller) filesystem.Filesystem { + mockFileSystem := mock_filesystem.NewMockFilesystem(controller) return mockFileSystem }, getMountClient: func(controller *gomock.Controller) mount.Mount { @@ -635,7 +635,7 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` return fs }, publishInfo: models.VolumePublishInfo{ - FilesystemType: config.FsExt4, + FilesystemType: filesystem.Ext4, VolumeAccessInfo: models.VolumeAccessInfo{ IscsiAccessInfo: models.IscsiAccessInfo{ IscsiTargetPortal: "127.0.0.1", @@ -670,8 +670,8 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` mockDevices := mock_iscsi.NewMockDevices(controller) return mockDevices }, - getFileSystemClient: func(controller *gomock.Controller) FileSystem { - mockFileSystem := mock_iscsi.NewMockFileSystem(controller) + getFileSystemClient: func(controller *gomock.Controller) filesystem.Filesystem { + mockFileSystem := mock_filesystem.NewMockFilesystem(controller) return mockFileSystem }, getMountClient: func(controller *gomock.Controller) mount.Mount { @@ -699,7 +699,7 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` return fs }, publishInfo: models.VolumePublishInfo{ - FilesystemType: config.FsExt4, + FilesystemType: filesystem.Ext4, VolumeAccessInfo: models.VolumeAccessInfo{ IscsiAccessInfo: models.IscsiAccessInfo{ IscsiTargetPortal: "127.0.0.1", @@ -746,8 +746,8 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` mockDevices := mock_iscsi.NewMockDevices(controller) return mockDevices }, - getFileSystemClient: func(controller *gomock.Controller) FileSystem { - mockFileSystem := mock_iscsi.NewMockFileSystem(controller) + getFileSystemClient: func(controller *gomock.Controller) filesystem.Filesystem { + mockFileSystem := mock_filesystem.NewMockFilesystem(controller) return mockFileSystem }, getMountClient: func(controller *gomock.Controller) mount.Mount { @@ -778,7 +778,7 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` return fs }, publishInfo: models.VolumePublishInfo{ - FilesystemType: config.FsExt4, + FilesystemType: filesystem.Ext4, VolumeAccessInfo: models.VolumeAccessInfo{ IscsiAccessInfo: models.IscsiAccessInfo{ IscsiTargetPortal: "127.0.0.1", @@ -814,8 +814,8 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` mockDevices := mock_iscsi.NewMockDevices(controller) return mockDevices }, - getFileSystemClient: func(controller *gomock.Controller) FileSystem { - mockFileSystem := mock_iscsi.NewMockFileSystem(controller) + getFileSystemClient: func(controller *gomock.Controller) filesystem.Filesystem { + mockFileSystem := mock_filesystem.NewMockFilesystem(controller) return mockFileSystem }, getMountClient: func(controller *gomock.Controller) mount.Mount { @@ -846,7 +846,7 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` return fs }, publishInfo: models.VolumePublishInfo{ - FilesystemType: config.FsExt4, + FilesystemType: filesystem.Ext4, VolumeAccessInfo: models.VolumeAccessInfo{ IscsiAccessInfo: models.IscsiAccessInfo{ IscsiTargetPortal: "127.0.0.1", @@ -882,8 +882,8 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` mockDevices := mock_iscsi.NewMockDevices(controller) return mockDevices }, - getFileSystemClient: func(controller *gomock.Controller) FileSystem { - mockFileSystem := mock_iscsi.NewMockFileSystem(controller) + getFileSystemClient: func(controller *gomock.Controller) filesystem.Filesystem { + mockFileSystem := mock_filesystem.NewMockFilesystem(controller) return mockFileSystem }, getMountClient: func(controller *gomock.Controller) mount.Mount { @@ -915,7 +915,7 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` return fs }, publishInfo: models.VolumePublishInfo{ - FilesystemType: config.FsExt4, + FilesystemType: filesystem.Ext4, VolumeAccessInfo: models.VolumeAccessInfo{ IscsiAccessInfo: models.IscsiAccessInfo{ IscsiTargetPortal: "127.0.0.1", @@ -951,8 +951,8 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` mockDevices := mock_iscsi.NewMockDevices(controller) return mockDevices }, - getFileSystemClient: func(controller *gomock.Controller) FileSystem { - mockFileSystem := mock_iscsi.NewMockFileSystem(controller) + getFileSystemClient: func(controller *gomock.Controller) filesystem.Filesystem { + mockFileSystem := mock_filesystem.NewMockFilesystem(controller) return mockFileSystem }, getMountClient: func(controller *gomock.Controller) mount.Mount { @@ -988,7 +988,7 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` return fs }, publishInfo: models.VolumePublishInfo{ - FilesystemType: config.FsExt4, + FilesystemType: filesystem.Ext4, VolumeAccessInfo: models.VolumeAccessInfo{ IscsiAccessInfo: models.IscsiAccessInfo{ IscsiTargetPortal: "127.0.0.1", @@ -1024,8 +1024,8 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` mockDevices := mock_iscsi.NewMockDevices(controller) return mockDevices }, - getFileSystemClient: func(controller *gomock.Controller) FileSystem { - mockFileSystem := mock_iscsi.NewMockFileSystem(controller) + getFileSystemClient: func(controller *gomock.Controller) filesystem.Filesystem { + mockFileSystem := mock_filesystem.NewMockFilesystem(controller) return mockFileSystem }, getMountClient: func(controller *gomock.Controller) mount.Mount { @@ -1061,7 +1061,7 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` return fs }, publishInfo: models.VolumePublishInfo{ - FilesystemType: config.FsExt4, + FilesystemType: filesystem.Ext4, VolumeAccessInfo: models.VolumeAccessInfo{ IscsiAccessInfo: models.IscsiAccessInfo{ IscsiTargetPortal: "127.0.0.1", @@ -1099,8 +1099,8 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` errors.New("some error")) return mockDevices }, - getFileSystemClient: func(controller *gomock.Controller) FileSystem { - mockFileSystem := mock_iscsi.NewMockFileSystem(controller) + getFileSystemClient: func(controller *gomock.Controller) filesystem.Filesystem { + mockFileSystem := mock_filesystem.NewMockFilesystem(controller) return mockFileSystem }, getMountClient: func(controller *gomock.Controller) mount.Mount { @@ -1136,7 +1136,7 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` return fs }, publishInfo: models.VolumePublishInfo{ - FilesystemType: config.FsExt4, + FilesystemType: filesystem.Ext4, VolumeAccessInfo: models.VolumeAccessInfo{ IscsiAccessInfo: models.IscsiAccessInfo{ IscsiTargetPortal: "127.0.0.1", @@ -1175,8 +1175,8 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` mockDevices.EXPECT().WaitForDevice(context.TODO(), "/dev/dm-0").Return(errors.New("some error")) return mockDevices }, - getFileSystemClient: func(controller *gomock.Controller) FileSystem { - mockFileSystem := mock_iscsi.NewMockFileSystem(controller) + getFileSystemClient: func(controller *gomock.Controller) filesystem.Filesystem { + mockFileSystem := mock_filesystem.NewMockFilesystem(controller) return mockFileSystem }, getMountClient: func(controller *gomock.Controller) mount.Mount { @@ -1212,7 +1212,7 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` return fs }, publishInfo: models.VolumePublishInfo{ - FilesystemType: config.FsExt4, + FilesystemType: filesystem.Ext4, VolumeAccessInfo: models.VolumeAccessInfo{ IscsiAccessInfo: models.IscsiAccessInfo{ IscsiTargetPortal: "127.0.0.1", @@ -1251,8 +1251,8 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` mockDevices.EXPECT().WaitForDevice(context.TODO(), "/dev/dm-0").Return(nil) return mockDevices }, - getFileSystemClient: func(controller *gomock.Controller) FileSystem { - mockFileSystem := mock_iscsi.NewMockFileSystem(controller) + getFileSystemClient: func(controller *gomock.Controller) filesystem.Filesystem { + mockFileSystem := mock_filesystem.NewMockFilesystem(controller) return mockFileSystem }, getMountClient: func(controller *gomock.Controller) mount.Mount { @@ -1289,7 +1289,7 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` }, publishInfo: models.VolumePublishInfo{ LUKSEncryption: "foo", - FilesystemType: config.FsExt4, + FilesystemType: filesystem.Ext4, VolumeAccessInfo: models.VolumeAccessInfo{ IscsiAccessInfo: models.IscsiAccessInfo{ IscsiTargetPortal: "127.0.0.1", @@ -1331,8 +1331,8 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` map[string]string{}).Return(false, errors.New("some error")) return mockDevices }, - getFileSystemClient: func(controller *gomock.Controller) FileSystem { - mockFileSystem := mock_iscsi.NewMockFileSystem(controller) + getFileSystemClient: func(controller *gomock.Controller) filesystem.Filesystem { + mockFileSystem := mock_filesystem.NewMockFilesystem(controller) return mockFileSystem }, getMountClient: func(controller *gomock.Controller) mount.Mount { @@ -1369,7 +1369,7 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` }, publishInfo: models.VolumePublishInfo{ LUKSEncryption: "true", - FilesystemType: config.FsExt4, + FilesystemType: filesystem.Ext4, VolumeAccessInfo: models.VolumeAccessInfo{ IscsiAccessInfo: models.IscsiAccessInfo{ IscsiTargetPortal: "127.0.0.1", @@ -1415,8 +1415,8 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` return mockDevices }, - getFileSystemClient: func(controller *gomock.Controller) FileSystem { - mockFileSystem := mock_iscsi.NewMockFileSystem(controller) + getFileSystemClient: func(controller *gomock.Controller) filesystem.Filesystem { + mockFileSystem := mock_filesystem.NewMockFilesystem(controller) return mockFileSystem }, getMountClient: func(controller *gomock.Controller) mount.Mount { @@ -1453,7 +1453,7 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` }, publishInfo: models.VolumePublishInfo{ LUKSEncryption: "true", - FilesystemType: config.FsRaw, + FilesystemType: filesystem.Raw, VolumeAccessInfo: models.VolumeAccessInfo{ IscsiAccessInfo: models.IscsiAccessInfo{ IscsiTargetPortal: "127.0.0.1", @@ -1501,8 +1501,8 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` return mockDevices }, - getFileSystemClient: func(controller *gomock.Controller) FileSystem { - mockFileSystem := mock_iscsi.NewMockFileSystem(controller) + getFileSystemClient: func(controller *gomock.Controller) filesystem.Filesystem { + mockFileSystem := mock_filesystem.NewMockFilesystem(controller) return mockFileSystem }, getMountClient: func(controller *gomock.Controller) mount.Mount { @@ -1539,7 +1539,7 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` }, publishInfo: models.VolumePublishInfo{ LUKSEncryption: "true", - FilesystemType: config.FsExt4, + FilesystemType: filesystem.Ext4, VolumeAccessInfo: models.VolumeAccessInfo{ IscsiAccessInfo: models.IscsiAccessInfo{ IscsiTargetPortal: "127.0.0.1", @@ -1587,8 +1587,8 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` return mockDevices }, - getFileSystemClient: func(controller *gomock.Controller) FileSystem { - mockFileSystem := mock_iscsi.NewMockFileSystem(controller) + getFileSystemClient: func(controller *gomock.Controller) filesystem.Filesystem { + mockFileSystem := mock_filesystem.NewMockFilesystem(controller) return mockFileSystem }, getMountClient: func(controller *gomock.Controller) mount.Mount { @@ -1625,7 +1625,7 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` }, publishInfo: models.VolumePublishInfo{ LUKSEncryption: "true", - FilesystemType: config.FsExt4, + FilesystemType: filesystem.Ext4, VolumeAccessInfo: models.VolumeAccessInfo{ IscsiAccessInfo: models.IscsiAccessInfo{ IscsiTargetPortal: "127.0.0.1", @@ -1666,8 +1666,8 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` mockDevices.EXPECT().IsDeviceUnformatted(context.TODO(), "/dev/dm-0").Return(false, errors.New("some error")) return mockDevices }, - getFileSystemClient: func(controller *gomock.Controller) FileSystem { - mockFileSystem := mock_iscsi.NewMockFileSystem(controller) + getFileSystemClient: func(controller *gomock.Controller) filesystem.Filesystem { + mockFileSystem := mock_filesystem.NewMockFilesystem(controller) return mockFileSystem }, getMountClient: func(controller *gomock.Controller) mount.Mount { @@ -1703,7 +1703,7 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` return fs }, publishInfo: models.VolumePublishInfo{ - FilesystemType: config.FsExt4, + FilesystemType: filesystem.Ext4, VolumeAccessInfo: models.VolumeAccessInfo{ IscsiAccessInfo: models.IscsiAccessInfo{ IscsiTargetPortal: "127.0.0.1", @@ -1744,8 +1744,8 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` mockDevices.EXPECT().IsDeviceUnformatted(context.TODO(), "/dev/dm-0").Return(false, nil) return mockDevices }, - getFileSystemClient: func(controller *gomock.Controller) FileSystem { - mockFileSystem := mock_iscsi.NewMockFileSystem(controller) + getFileSystemClient: func(controller *gomock.Controller) filesystem.Filesystem { + mockFileSystem := mock_filesystem.NewMockFilesystem(controller) return mockFileSystem }, getMountClient: func(controller *gomock.Controller) mount.Mount { @@ -1781,7 +1781,7 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` return fs }, publishInfo: models.VolumePublishInfo{ - FilesystemType: config.FsExt4, + FilesystemType: filesystem.Ext4, VolumeAccessInfo: models.VolumeAccessInfo{ IscsiAccessInfo: models.IscsiAccessInfo{ IscsiTargetPortal: "127.0.0.1", @@ -1822,9 +1822,9 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` mockDevices.EXPECT().IsDeviceUnformatted(context.TODO(), "/dev/dm-0").Return(true, nil) return mockDevices }, - getFileSystemClient: func(controller *gomock.Controller) FileSystem { - mockFileSystem := mock_iscsi.NewMockFileSystem(controller) - mockFileSystem.EXPECT().FormatVolume(context.TODO(), "/dev/dm-0", config.FsExt4, "").Return(errors.New("some error")) + getFileSystemClient: func(controller *gomock.Controller) filesystem.Filesystem { + mockFileSystem := mock_filesystem.NewMockFilesystem(controller) + mockFileSystem.EXPECT().FormatVolume(context.TODO(), "/dev/dm-0", filesystem.Ext4, "").Return(errors.New("some error")) return mockFileSystem }, getMountClient: func(controller *gomock.Controller) mount.Mount { @@ -1860,7 +1860,7 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` return fs }, publishInfo: models.VolumePublishInfo{ - FilesystemType: config.FsExt4, + FilesystemType: filesystem.Ext4, VolumeAccessInfo: models.VolumeAccessInfo{ IscsiAccessInfo: models.IscsiAccessInfo{ IscsiTargetPortal: "127.0.0.1", @@ -1897,11 +1897,11 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` mockDevices.EXPECT().GetISCSIDiskSize(context.TODO(), "/dev/sda").Return(int64(0), nil) mockDevices.EXPECT().GetISCSIDiskSize(context.TODO(), "/dev/dm-0").Return(int64(0), nil) mockDevices.EXPECT().WaitForDevice(context.TODO(), "/dev/dm-0").Return(nil) - mockDevices.EXPECT().GetDeviceFSType(context.TODO(), "/dev/dm-0").Return(config.FsExt3, nil) + mockDevices.EXPECT().GetDeviceFSType(context.TODO(), "/dev/dm-0").Return(filesystem.Ext3, nil) return mockDevices }, - getFileSystemClient: func(controller *gomock.Controller) FileSystem { - mockFileSystem := mock_iscsi.NewMockFileSystem(controller) + getFileSystemClient: func(controller *gomock.Controller) filesystem.Filesystem { + mockFileSystem := mock_filesystem.NewMockFilesystem(controller) return mockFileSystem }, getMountClient: func(controller *gomock.Controller) mount.Mount { @@ -1937,7 +1937,7 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` return fs }, publishInfo: models.VolumePublishInfo{ - FilesystemType: config.FsExt4, + FilesystemType: filesystem.Ext4, VolumeAccessInfo: models.VolumeAccessInfo{ IscsiAccessInfo: models.IscsiAccessInfo{ IscsiTargetPortal: "127.0.0.1", @@ -1977,8 +1977,8 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` mockDevices.EXPECT().GetDeviceFSType(context.TODO(), "/dev/dm-0").Return(unknownFstype, nil) return mockDevices }, - getFileSystemClient: func(controller *gomock.Controller) FileSystem { - mockFileSystem := mock_iscsi.NewMockFileSystem(controller) + getFileSystemClient: func(controller *gomock.Controller) filesystem.Filesystem { + mockFileSystem := mock_filesystem.NewMockFilesystem(controller) return mockFileSystem }, getMountClient: func(controller *gomock.Controller) mount.Mount { @@ -2015,7 +2015,7 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` return fs }, publishInfo: models.VolumePublishInfo{ - FilesystemType: config.FsExt4, + FilesystemType: filesystem.Ext4, VolumeAccessInfo: models.VolumeAccessInfo{ IscsiAccessInfo: models.IscsiAccessInfo{ IscsiTargetPortal: "127.0.0.1", @@ -2055,9 +2055,9 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` mockDevices.EXPECT().GetDeviceFSType(context.TODO(), "/dev/dm-0").Return(unknownFstype, nil) return mockDevices }, - getFileSystemClient: func(controller *gomock.Controller) FileSystem { - mockFileSystem := mock_iscsi.NewMockFileSystem(controller) - mockFileSystem.EXPECT().RepairVolume(context.TODO(), "/dev/dm-0", config.FsExt4) + getFileSystemClient: func(controller *gomock.Controller) filesystem.Filesystem { + mockFileSystem := mock_filesystem.NewMockFilesystem(controller) + mockFileSystem.EXPECT().RepairVolume(context.TODO(), "/dev/dm-0", filesystem.Ext4) return mockFileSystem }, getMountClient: func(controller *gomock.Controller) mount.Mount { @@ -2094,7 +2094,7 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` return fs }, publishInfo: models.VolumePublishInfo{ - FilesystemType: config.FsExt4, + FilesystemType: filesystem.Ext4, VolumeAccessInfo: models.VolumeAccessInfo{ IscsiAccessInfo: models.IscsiAccessInfo{ IscsiTargetPortal: "127.0.0.1", @@ -2134,9 +2134,9 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` mockDevices.EXPECT().GetDeviceFSType(context.TODO(), "/dev/dm-0").Return(unknownFstype, nil) return mockDevices }, - getFileSystemClient: func(controller *gomock.Controller) FileSystem { - mockFileSystem := mock_iscsi.NewMockFileSystem(controller) - mockFileSystem.EXPECT().RepairVolume(context.TODO(), "/dev/dm-0", config.FsExt4) + getFileSystemClient: func(controller *gomock.Controller) filesystem.Filesystem { + mockFileSystem := mock_filesystem.NewMockFilesystem(controller) + mockFileSystem.EXPECT().RepairVolume(context.TODO(), "/dev/dm-0", filesystem.Ext4) return mockFileSystem }, getMountClient: func(controller *gomock.Controller) mount.Mount { @@ -2175,7 +2175,7 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` return fs }, publishInfo: models.VolumePublishInfo{ - FilesystemType: config.FsExt4, + FilesystemType: filesystem.Ext4, VolumeAccessInfo: models.VolumeAccessInfo{ IscsiAccessInfo: models.IscsiAccessInfo{ IscsiTargetPortal: "127.0.0.1", @@ -2215,9 +2215,9 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` mockDevices.EXPECT().GetDeviceFSType(context.TODO(), "/dev/dm-0").Return(unknownFstype, nil) return mockDevices }, - getFileSystemClient: func(controller *gomock.Controller) FileSystem { - mockFileSystem := mock_iscsi.NewMockFileSystem(controller) - mockFileSystem.EXPECT().RepairVolume(context.TODO(), "/dev/dm-0", config.FsExt4) + getFileSystemClient: func(controller *gomock.Controller) filesystem.Filesystem { + mockFileSystem := mock_filesystem.NewMockFilesystem(controller) + mockFileSystem.EXPECT().RepairVolume(context.TODO(), "/dev/dm-0", filesystem.Ext4) return mockFileSystem }, getMountClient: func(controller *gomock.Controller) mount.Mount { @@ -2256,7 +2256,7 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` return fs }, publishInfo: models.VolumePublishInfo{ - FilesystemType: config.FsExt4, + FilesystemType: filesystem.Ext4, VolumeAccessInfo: models.VolumeAccessInfo{ IscsiAccessInfo: models.IscsiAccessInfo{ IscsiTargetPortal: "127.0.0.1", @@ -2351,7 +2351,7 @@ func TestClient_AddSession(t *testing.T) { for name, params := range tests { t.Run(name, func(t *testing.T) { - client, err := New(nil, nil, nil) + client, err := New(nil, nil) assert.NoError(t, err) ctx := context.WithValue(context.TODO(), SessionInfoSource, "test") client.AddSession(ctx, params.sessions, ¶ms.publishInfo, params.volID, @@ -3155,7 +3155,7 @@ func TestClient_getDeviceInfoForLUN(t *testing.T) { getDevicesClient: func(controller *gomock.Controller) Devices { mockDeviceClient := mock_iscsi.NewMockDevices(controller) mockDeviceClient.EXPECT().EnsureDeviceReadable(context.TODO(), multipathDevicePath).Return(nil) - mockDeviceClient.EXPECT().GetDeviceFSType(context.TODO(), multipathDevicePath).Return(config.FsExt4, nil) + mockDeviceClient.EXPECT().GetDeviceFSType(context.TODO(), multipathDevicePath).Return(filesystem.Ext4, nil) return mockDeviceClient }, getFileSystemUtils: func() afero.Fs { @@ -3171,7 +3171,7 @@ func TestClient_getDeviceInfoForLUN(t *testing.T) { DevicePaths: []string{devicePath}, MultipathDevice: multipathDeviceName, IQN: iscisNodeName, - Filesystem: config.FsExt4, + Filesystem: filesystem.Ext4, }, }, } diff --git a/utils/models/types.go b/utils/models/types.go index 78d03f40f..e4cae460d 100644 --- a/utils/models/types.go +++ b/utils/models/types.go @@ -2,7 +2,6 @@ package models -//go:generate mockgen -destination=../../mocks/mock_utils/mock_models/mock_json_utils.go github.com/netapp/trident/utils/models JSONReaderWriter //go:generate mockgen -destination=../../mocks/mock_utils/mock_models/mock_luks/mock_luks.go -package mock_luks github.com/netapp/trident/utils/models LUKSDeviceInterface import ( @@ -103,6 +102,12 @@ type NVMeAccessInfo struct { NVMeNamespaceUUID string `json:"nvmeNamespaceUUID,omitempty"` } +// DFInfo data structure for wrapping the parsed output from the 'df' command +type DFInfo struct { + Target string + Source string +} + type VolumePublishInfo struct { Localhost bool `json:"localhost,omitempty"` HostIQN []string `json:"hostIQN,omitempty"` @@ -320,11 +325,6 @@ type NodePrepBreadcrumb struct { ISCSI string `json:"iscsi,omitempty"` } -type JSONReaderWriter interface { - WriteJSONFile(ctx context.Context, fileContents interface{}, filepath, fileDescription string) error - ReadJSONFile(ctx context.Context, fileContents interface{}, filepath, fileDescription string) error -} - // Data structure and related interfaces to help iSCSI self-healing // PortalInvalid is a data structure for iSCSI self-healing capturing Portal's invalid non-recoverable state diff --git a/utils/nvme.go b/utils/nvme.go index a0e6e8114..2454adad5 100644 --- a/utils/nvme.go +++ b/utils/nvme.go @@ -14,11 +14,14 @@ import ( . "github.com/netapp/trident/logging" "github.com/netapp/trident/utils/errors" + "github.com/netapp/trident/utils/filesystem" "github.com/netapp/trident/utils/models" ) const NVMeAttachTimeout = 20 * time.Second +var fsClient = filesystem.New(mountClient) + // getNVMeSubsystem returns the NVMe subsystem details. func getNVMeSubsystem(ctx context.Context, subsysNqn string) (*NVMeSubsystem, error) { Logc(ctx).Debug(">>>> nvme.getNVMeSubsystem") @@ -303,7 +306,7 @@ func AttachNVMeVolume( } func NVMeMountVolume(ctx context.Context, name, mountpoint string, publishInfo *models.VolumePublishInfo) error { - if publishInfo.FilesystemType == fsRaw { + if publishInfo.FilesystemType == filesystem.Raw { return nil } devicePath := publishInfo.DevicePath @@ -322,7 +325,7 @@ func NVMeMountVolume(ctx context.Context, name, mountpoint string, publishInfo * return fmt.Errorf("device %v is not unformatted", devicePath) } Logc(ctx).WithFields(LogFields{"volume": name, "fstype": publishInfo.FilesystemType}).Debug("Formatting LUN.") - err := formatVolume(ctx, devicePath, publishInfo.FilesystemType, publishInfo.FormatOptions) + err := fsClient.FormatVolume(ctx, devicePath, publishInfo.FilesystemType, publishInfo.FormatOptions) if err != nil { return fmt.Errorf("error formatting Namespace %s, device %s: %v", name, devicePath, err) } @@ -350,7 +353,7 @@ func NVMeMountVolume(ctx context.Context, name, mountpoint string, publishInfo * return err } if !mounted { - repairVolume(ctx, devicePath, publishInfo.FilesystemType) + fsClient.RepairVolume(ctx, devicePath, publishInfo.FilesystemType) } // Optionally mount the device diff --git a/utils/osutils_linux.go b/utils/osutils_linux.go index b1f553817..4e2a08232 100644 --- a/utils/osutils_linux.go +++ b/utils/osutils_linux.go @@ -14,18 +14,12 @@ import ( "github.com/vishvananda/netlink" "github.com/zcalusic/sysinfo" - "golang.org/x/sys/unix" . "github.com/netapp/trident/logging" "github.com/netapp/trident/utils/errors" "github.com/netapp/trident/utils/models" ) -type statFSResult struct { - Output unix.Statfs_t - Error error -} - // NFSActiveOnHost will return if the rpc-statd daemon is active on the given host func NFSActiveOnHost(ctx context.Context) (bool, error) { Logc(ctx).Debug(">>>> osutils_linux.NFSActiveOnHost") diff --git a/utils/utils.go b/utils/utils.go index c8795d2a4..a65d7cc58 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -888,17 +888,6 @@ func RedactSecretsFromString(stringToSanitize string, replacements map[string]st return stringToSanitize } -// VerifyFilesystemSupport checks for a supported file system type -func VerifyFilesystemSupport(fs string) (string, error) { - fstype := strings.ToLower(fs) - switch fstype { - case fsXfs, fsExt3, fsExt4, fsRaw: - return fstype, nil - default: - return "", fmt.Errorf("unsupported fileSystemType option: %s", fstype) - } -} - // AppendToStringList appends an item to a string list with a seperator func AppendToStringList(stringList, newItem, sep string) string { stringListItems := SplitString(context.TODO(), stringList, sep) diff --git a/utils/utils_test.go b/utils/utils_test.go index 4a6ef9a4f..08e83a3ad 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -6,31 +6,20 @@ import ( "context" "fmt" "io" - "io/fs" "math/rand" "os" "strings" "testing" "time" - "github.com/spf13/afero" "github.com/stretchr/testify/assert" . "github.com/netapp/trident/logging" + "github.com/netapp/trident/utils/filesystem" "github.com/netapp/trident/utils/models" versionutils "github.com/netapp/trident/utils/version" ) -// MockFs is a struct that embeds the afero in-memory filesystem, so that we can -// implement a version of Open that can return a permissions error. -type MockFs struct { - afero.MemMapFs -} - -func (m *MockFs) Open(name string) (afero.File, error) { - return nil, fs.ErrPermission -} - var testStringSlice = []string{ "foo", "bar", @@ -1030,7 +1019,7 @@ func TestVerifyFilesystemSupport(t *testing.T) { } for _, test := range tests { - fsType, err := VerifyFilesystemSupport(test.fsType) + fsType, err := filesystem.VerifyFilesystemSupport(test.fsType) assert.Equal(t, test.outputFsType, fsType) assert.Equal(t, test.errNotNil, err != nil)