From 395ba1962c74aa50a0b03a9c1a6c6a5bf3cea3fa Mon Sep 17 00:00:00 2001 From: jharrod Date: Fri, 20 Dec 2024 13:43:33 -0700 Subject: [PATCH] Os and io refactor Refactor osutils into its own package Add osutils unit tests --- core/orchestrator_core.go | 5 +- frontend/csi/node_server.go | 17 +- frontend/csi/node_server_test.go | 12 +- frontend/csi/plugin.go | 14 +- .../nodeprep/instruction/instructions_test.go | 24 +- internal/nodeprep/nodeinfo/node_info.go | 12 +- internal/nodeprep/nodeinfo/node_info_test.go | 26 +- internal/nodeprep/nodeinfo/utils.go | 19 -- main.go | 4 +- .../mock_iscsi/mock_iscsi_client.go | 15 ++ mocks/mock_utils/mock_osutils/mock_netlink.go | 100 +++++++ mocks/mock_utils/mock_osutils/mock_osutils.go | 188 +++++++++++++ storage_drivers/ontap/ontap_san.go | 2 +- storage_drivers/ontap/ontap_san_economy.go | 2 +- .../ontap/ontap_san_economy_test.go | 2 +- storage_drivers/ontap/ontap_san_test.go | 3 +- storage_drivers/solidfire/solidfire_san.go | 2 +- utils/adaptors.go | 10 - utils/fcp.go | 10 +- utils/fcp/fcp.go | 11 +- utils/iscsi.go | 8 +- utils/iscsi/iscsi.go | 22 +- utils/{ => iscsi}/iscsi_darwin.go | 4 +- utils/{ => iscsi}/iscsi_darwin_test.go | 6 +- utils/{ => iscsi}/iscsi_linux.go | 13 +- utils/iscsi/iscsi_linux_test.go | 99 ++++++- utils/iscsi/iscsi_test.go | 51 ++-- utils/{ => iscsi}/iscsi_windows.go | 8 +- utils/{ => iscsi}/iscsi_windows_test.go | 6 +- utils/osutils/netlink_linux.go | 36 +++ utils/{ => osutils}/osutils.go | 89 +++++-- utils/{ => osutils}/osutils_darwin.go | 20 +- utils/{ => osutils}/osutils_darwin_test.go | 17 +- utils/{ => osutils}/osutils_linux.go | 46 ++-- utils/osutils/osutils_linux_test.go | 248 ++++++++++++++++++ utils/osutils/osutils_test.go | 220 ++++++++++++++++ utils/{ => osutils}/osutils_windows.go | 19 +- utils/{ => osutils}/osutils_windows_test.go | 11 +- utils/osutils_linux_test.go | 235 ----------------- utils/osutils_test.go | 50 ---- utils/utils.go | 10 - 41 files changed, 1182 insertions(+), 514 deletions(-) create mode 100644 mocks/mock_utils/mock_osutils/mock_netlink.go create mode 100644 mocks/mock_utils/mock_osutils/mock_osutils.go rename utils/{ => iscsi}/iscsi_darwin.go (82%) rename utils/{ => iscsi}/iscsi_darwin_test.go (82%) rename utils/{ => iscsi}/iscsi_linux.go (57%) rename utils/{ => iscsi}/iscsi_windows.go (62%) rename utils/{ => iscsi}/iscsi_windows_test.go (73%) create mode 100644 utils/osutils/netlink_linux.go rename utils/{ => osutils}/osutils.go (65%) rename utils/{ => osutils}/osutils_darwin.go (72%) rename utils/{ => osutils}/osutils_darwin_test.go (82%) rename utils/{ => osutils}/osutils_linux.go (80%) create mode 100644 utils/osutils/osutils_linux_test.go create mode 100644 utils/osutils/osutils_test.go rename utils/{ => osutils}/osutils_windows.go (74%) rename utils/{ => osutils}/osutils_windows_test.go (85%) delete mode 100644 utils/osutils_linux_test.go delete mode 100644 utils/osutils_test.go diff --git a/core/orchestrator_core.go b/core/orchestrator_core.go index 7c52344bc..a98ee54f9 100644 --- a/core/orchestrator_core.go +++ b/core/orchestrator_core.go @@ -109,9 +109,8 @@ type TridentOrchestrator struct { // NewTridentOrchestrator returns a storage orchestrator instance 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()) + iscsiClient, err := iscsi.New() if err != nil { return nil, err } @@ -121,7 +120,7 @@ func NewTridentOrchestrator(client persistentstore.Client) (*TridentOrchestrator return nil, err } - fcpClent, err := fcp.New(utils.NewOSClient(), filesystem.New(mountClient)) + fcpClent, err := fcp.New() if err != nil { return nil, err } diff --git a/frontend/csi/node_server.go b/frontend/csi/node_server.go index bc9c759b8..57d4db1f5 100644 --- a/frontend/csi/node_server.go +++ b/frontend/csi/node_server.go @@ -33,6 +33,7 @@ import ( "github.com/netapp/trident/utils/filesystem" "github.com/netapp/trident/utils/iscsi" "github.com/netapp/trident/utils/models" + "github.com/netapp/trident/utils/osutils" ) const ( @@ -263,7 +264,7 @@ func (p *Plugin) NodeUnpublishVolume( return nil, status.Error(codes.InvalidArgument, "no target path provided") } - isDir, err := utils.IsLikelyDir(targetPath) + isDir, err := p.osutils.IsLikelyDir(targetPath) if err != nil { if os.IsNotExist(err) { Logc(ctx).WithFields(fields).Infof("target path (%s) not found; volume is not mounted.", targetPath) @@ -305,7 +306,7 @@ func (p *Plugin) NodeUnpublishVolume( // however today Kubernetes performs this deletion. Here we are making best efforts // to delete the resource at target path. Sometimes this fails resulting CSI calling // NodeUnpublishVolume again and usually deletion goes through in the second attempt. - if err = utils.DeleteResourceAtPath(ctx, targetPath); err != nil { + if err = p.osutils.DeleteResourceAtPath(ctx, targetPath); err != nil { Logc(ctx).Debugf("Unable to delete resource at target path: %s; %s", targetPath, err) } @@ -340,7 +341,7 @@ func (p *Plugin) NodeGetVolumeStats( } // Ensure volume is published at path - exists, err := utils.PathExists(req.GetVolumePath()) + exists, err := p.osutils.PathExists(req.GetVolumePath()) if !exists || err != nil { return nil, status.Error(codes.NotFound, fmt.Sprintf("could not find volume mount at path: %s; %v", req.GetVolumePath(), err)) @@ -655,7 +656,7 @@ func (p *Plugin) NodeGetInfo( func (p *Plugin) nodeGetInfo(ctx context.Context) *models.Node { // Only get the host system info if we don't have the info yet. if p.hostInfo == nil { - host, err := utils.GetHostSystemInfo(ctx) + host, err := p.osutils.GetHostSystemInfo(ctx) if err != nil { p.hostInfo = &models.HostSystem{} Logc(ctx).WithError(err).Warn("Unable to get host system information.") @@ -680,7 +681,7 @@ func (p *Plugin) nodeGetInfo(ctx context.Context) *models.Node { Logc(ctx).WithField("IQN", iscsiWWN).Info("Discovered iSCSI initiator name.") } - ips, err := utils.GetIPAddresses(ctx) + ips, err := p.osutils.GetIPAddresses(ctx) if err != nil { Logc(ctx).WithField("error", err).Error("Could not get IP addresses.") } else if len(ips) == 0 { @@ -696,7 +697,7 @@ func (p *Plugin) nodeGetInfo(ctx context.Context) *models.Node { // Discover active protocol services on the host. var services []string - nfsActive, err := utils.NFSActiveOnHost(ctx) + nfsActive, err := p.osutils.NFSActiveOnHost(ctx) if err != nil { Logc(ctx).WithError(err).Warn("Error discovering NFS service on host.") } @@ -704,7 +705,7 @@ func (p *Plugin) nodeGetInfo(ctx context.Context) *models.Node { services = append(services, "NFS") } - smbActive, err := utils.SMBActiveOnHost(ctx) + smbActive, err := osutils.SMBActiveOnHost(ctx) if err != nil { Logc(ctx).WithError(err).Warn("Error discovering SMB service on host.") } @@ -712,7 +713,7 @@ func (p *Plugin) nodeGetInfo(ctx context.Context) *models.Node { services = append(services, "SMB") } - iscsiActive, err := utils.ISCSIActiveOnHost(ctx, *p.hostInfo) + iscsiActive, err := p.iscsi.ISCSIActiveOnHost(ctx, *p.hostInfo) if err != nil { Logc(ctx).WithError(err).Warn("Error discovering iSCSI service on host.") } diff --git a/frontend/csi/node_server_test.go b/frontend/csi/node_server_test.go index 2818714e0..2a75e31af 100644 --- a/frontend/csi/node_server_test.go +++ b/frontend/csi/node_server_test.go @@ -33,6 +33,7 @@ import ( "github.com/netapp/trident/utils/iscsi" "github.com/netapp/trident/utils/models" "github.com/netapp/trident/utils/mount" + "github.com/netapp/trident/utils/osutils" ) func TestNodeStageVolume(t *testing.T) { @@ -1025,7 +1026,7 @@ func TestFixISCSISessions(t *testing.T) { }, } - iscsiClient, err := iscsi.New(utils.NewOSClient()) + iscsiClient, err := iscsi.New() assert.NoError(t, err) nodeServer := &Plugin{ @@ -1750,6 +1751,7 @@ func TestNodeRegisterWithController_Success(t *testing.T) { mockOrchestrator := mockcore.NewMockOrchestrator(mockCtrl) mockNVMeHandler := mockUtils.NewMockNVMeInterface(mockCtrl) + iscsiClient, _ := iscsi.New() // Create a node server plugin nodeServer := &Plugin{ nodeName: nodeName, @@ -1758,6 +1760,8 @@ func TestNodeRegisterWithController_Success(t *testing.T) { restClient: mockClient, nvmeHandler: mockNVMeHandler, orchestrator: mockOrchestrator, + osutils: osutils.New(), + iscsi: iscsiClient, } // Create a fake node response to be returned by controller @@ -1791,6 +1795,7 @@ func TestNodeRegisterWithController_TopologyLabels(t *testing.T) { mockClient := mockControllerAPI.NewMockTridentController(mockCtrl) mockOrchestrator := mockcore.NewMockOrchestrator(mockCtrl) mockNVMeHandler := mockUtils.NewMockNVMeInterface(mockCtrl) + iscsiClient, _ := iscsi.New() // Create a node server plugin nodeServer := &Plugin{ @@ -1800,6 +1805,8 @@ func TestNodeRegisterWithController_TopologyLabels(t *testing.T) { restClient: mockClient, nvmeHandler: mockNVMeHandler, orchestrator: mockOrchestrator, + osutils: osutils.New(), + iscsi: iscsiClient, } // Create set of cases with varying topology labels @@ -1874,6 +1881,7 @@ func TestNodeRegisterWithController_Failure(t *testing.T) { mockClient := mockControllerAPI.NewMockTridentController(mockCtrl) mockOrchestrator := mockcore.NewMockOrchestrator(mockCtrl) mockNVMeHandler := mockUtils.NewMockNVMeInterface(mockCtrl) + iscsiClient, _ := iscsi.New() // Create a node server plugin nodeServer := &Plugin{ @@ -1883,6 +1891,8 @@ func TestNodeRegisterWithController_Failure(t *testing.T) { restClient: mockClient, nvmeHandler: mockNVMeHandler, orchestrator: mockOrchestrator, + iscsi: iscsiClient, + osutils: osutils.New(), } // Create a fake node response to be returned by controller diff --git a/frontend/csi/plugin.go b/frontend/csi/plugin.go index c1dadbe85..f5a241a7d 100644 --- a/frontend/csi/plugin.go +++ b/frontend/csi/plugin.go @@ -30,6 +30,7 @@ import ( "github.com/netapp/trident/utils/iscsi" "github.com/netapp/trident/utils/models" "github.com/netapp/trident/utils/mount" + "github.com/netapp/trident/utils/osutils" ) const ( @@ -91,6 +92,7 @@ type Plugin struct { mount mount.Mount fs filesystem.Filesystem fcp fcp.FCP + osutils osutils.Utils } func NewControllerPlugin( @@ -112,6 +114,7 @@ func NewControllerPlugin( enableForceDetach: enableForceDetach, opCache: sync.Map{}, command: execCmd.NewCommand(), + osutils: osutils.New(), } var err error @@ -164,9 +167,8 @@ func NewNodePlugin( } Logc(ctx).Info(msg) - // 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()) + iscsiClient, err := iscsi.New() if err != nil { return nil, err } @@ -178,7 +180,7 @@ func NewNodePlugin( fs := filesystem.New(mountClient) - fcpClient, err := fcp.New(utils.NewOSClient(), fs) + fcpClient, err := fcp.New() if err != nil { return nil, err } @@ -205,6 +207,7 @@ func NewNodePlugin( mount: mountClient, fs: fs, command: execCmd.NewCommand(), + osutils: osutils.New(), } if runtime.GOOS == "windows" { @@ -275,7 +278,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()) + iscsiClient, err := iscsi.New() if err != nil { return nil, err } @@ -287,7 +290,7 @@ func NewAllInOnePlugin( fs := filesystem.New(mountClient) - fcpClient, err := fcp.New(utils.NewOSClient(), fs) + fcpClient, err := fcp.New() if err != nil { return nil, err } @@ -313,6 +316,7 @@ func NewAllInOnePlugin( mount: mountClient, fs: fs, command: execCmd.NewCommand(), + osutils: osutils.New(), } // Define controller capabilities diff --git a/internal/nodeprep/instruction/instructions_test.go b/internal/nodeprep/instruction/instructions_test.go index 28a568b9c..7aed20c31 100644 --- a/internal/nodeprep/instruction/instructions_test.go +++ b/internal/nodeprep/instruction/instructions_test.go @@ -10,7 +10,9 @@ import ( "github.com/netapp/trident/internal/nodeprep/nodeinfo" "github.com/netapp/trident/internal/nodeprep/protocol" "github.com/netapp/trident/mocks/mock_internal/mock_nodeprep/mock_nodeinfo" + "github.com/netapp/trident/mocks/mock_utils/mock_osutils" "github.com/netapp/trident/utils/models" + "github.com/netapp/trident/utils/osutils" ) func TestInstructions(t *testing.T) { @@ -32,7 +34,7 @@ func TestInstructions(t *testing.T) { } type parameters struct { - getOSClient func(controller *gomock.Controller) nodeinfo.OS + getOSClient func(controller *gomock.Controller) osutils.Utils getBinaryClient func(controller *gomock.Controller) nodeinfo.Binary expectedNodeInfo *nodeinfo.NodeInfo setInstructions func() @@ -62,8 +64,8 @@ func TestInstructions(t *testing.T) { tests := map[string]parameters{ "node info returns supported distro ubuntu": { - getOSClient: func(controller *gomock.Controller) nodeinfo.OS { - os := mock_nodeinfo.NewMockOS(controller) + getOSClient: func(controller *gomock.Controller) osutils.Utils { + os := mock_osutils.NewMockUtils(controller) os.EXPECT().GetHostSystemInfo(gomock.Any()).Return(&ubuntuHostSystemResponse, nil) return os }, @@ -83,8 +85,8 @@ func TestInstructions(t *testing.T) { assertError: assert.NoError, }, "node info returns supported distro amazon linux": { - getOSClient: func(controller *gomock.Controller) nodeinfo.OS { - os := mock_nodeinfo.NewMockOS(controller) + getOSClient: func(controller *gomock.Controller) osutils.Utils { + os := mock_osutils.NewMockUtils(controller) os.EXPECT().GetHostSystemInfo(gomock.Any()).Return(&amazonHostSystemResponse, nil) return os }, @@ -103,8 +105,8 @@ func TestInstructions(t *testing.T) { assertError: assert.NoError, }, "node info returns unknown linux distro with package manager": { - getOSClient: func(controller *gomock.Controller) nodeinfo.OS { - os := mock_nodeinfo.NewMockOS(controller) + getOSClient: func(controller *gomock.Controller) osutils.Utils { + os := mock_osutils.NewMockUtils(controller) os.EXPECT().GetHostSystemInfo(gomock.Any()).Return(&fooHostSystemResponse, nil) return os }, @@ -123,8 +125,8 @@ func TestInstructions(t *testing.T) { assertError: assert.NoError, }, "node info returns unknown linux distro without package manager": { - getOSClient: func(controller *gomock.Controller) nodeinfo.OS { - os := mock_nodeinfo.NewMockOS(controller) + getOSClient: func(controller *gomock.Controller) osutils.Utils { + os := mock_osutils.NewMockUtils(controller) os.EXPECT().GetHostSystemInfo(gomock.Any()).Return(&fooHostSystemResponse, nil) return os }, @@ -144,8 +146,8 @@ func TestInstructions(t *testing.T) { assertError: assert.Error, }, "node info on host with no package manager": { - getOSClient: func(controller *gomock.Controller) nodeinfo.OS { - os := mock_nodeinfo.NewMockOS(controller) + getOSClient: func(controller *gomock.Controller) osutils.Utils { + os := mock_osutils.NewMockUtils(controller) os.EXPECT().GetHostSystemInfo(gomock.Any()).Return(&ubuntuHostSystemResponse, nil) return os }, diff --git a/internal/nodeprep/nodeinfo/node_info.go b/internal/nodeprep/nodeinfo/node_info.go index 358c43bb5..6b95d6952 100644 --- a/internal/nodeprep/nodeinfo/node_info.go +++ b/internal/nodeprep/nodeinfo/node_info.go @@ -9,8 +9,8 @@ import ( "strings" . "github.com/netapp/trident/logging" - "github.com/netapp/trident/utils" "github.com/netapp/trident/utils/models" + "github.com/netapp/trident/utils/osutils" ) type NodeInfo struct { @@ -25,7 +25,7 @@ type ( ) const ( - DistroUbuntu Distro = utils.Ubuntu + DistroUbuntu Distro = osutils.Ubuntu DistroAmzn Distro = "amzn" DistroRhcos Distro = "rhcos" DistroUnknown = "unknown" @@ -40,17 +40,17 @@ type Node interface { } type NodeClient struct { - os OS + os osutils.Utils binary Binary } func New() *NodeClient { - return NewDetailed(NewOSClient(), NewBinary()) + return NewDetailed(osutils.New(), NewBinary()) } -func NewDetailed(os OS, binary Binary) *NodeClient { +func NewDetailed(osUtils osutils.Utils, binary Binary) *NodeClient { return &NodeClient{ - os: os, + os: osUtils, binary: binary, } } diff --git a/internal/nodeprep/nodeinfo/node_info_test.go b/internal/nodeprep/nodeinfo/node_info_test.go index 34bf04238..cfcedb979 100644 --- a/internal/nodeprep/nodeinfo/node_info_test.go +++ b/internal/nodeprep/nodeinfo/node_info_test.go @@ -10,8 +10,10 @@ import ( "github.com/netapp/trident/internal/nodeprep/nodeinfo" "github.com/netapp/trident/mocks/mock_internal/mock_nodeprep/mock_nodeinfo" + "github.com/netapp/trident/mocks/mock_utils/mock_osutils" "github.com/netapp/trident/utils/errors" "github.com/netapp/trident/utils/models" + "github.com/netapp/trident/utils/osutils" ) func TestNew(t *testing.T) { @@ -21,7 +23,7 @@ func TestNew(t *testing.T) { func TestNewDetailed(t *testing.T) { ctrl := gomock.NewController(t) - os := mock_nodeinfo.NewMockOS(ctrl) + os := mock_osutils.NewMockUtils(ctrl) binary := mock_nodeinfo.NewMockBinary(ctrl) node := nodeinfo.NewDetailed(os, binary) assert.NotNil(t, node) @@ -29,7 +31,7 @@ func TestNewDetailed(t *testing.T) { func TestNode_GetInfo(t *testing.T) { type parameters struct { - getOSClient func(controller *gomock.Controller) nodeinfo.OS + getOSClient func(controller *gomock.Controller) osutils.Utils getBinaryClient func(controller *gomock.Controller) nodeinfo.Binary expectedNodeInfo *nodeinfo.NodeInfo assertError assert.ErrorAssertionFunc @@ -55,8 +57,8 @@ func TestNode_GetInfo(t *testing.T) { tests := map[string]parameters{ "error getting host system info": { - getOSClient: func(controller *gomock.Controller) nodeinfo.OS { - os := mock_nodeinfo.NewMockOS(controller) + getOSClient: func(controller *gomock.Controller) osutils.Utils { + os := mock_osutils.NewMockUtils(controller) os.EXPECT().GetHostSystemInfo(gomock.Any()).Return(nil, errors.New("some error")) return os }, @@ -68,8 +70,8 @@ func TestNode_GetInfo(t *testing.T) { assertError: assert.Error, }, "node info returns unsupported distro": { - getOSClient: func(controller *gomock.Controller) nodeinfo.OS { - os := mock_nodeinfo.NewMockOS(controller) + getOSClient: func(controller *gomock.Controller) osutils.Utils { + os := mock_osutils.NewMockUtils(controller) os.EXPECT().GetHostSystemInfo(gomock.Any()).Return(&fooHostSystemResponse, nil) return os }, @@ -86,8 +88,8 @@ func TestNode_GetInfo(t *testing.T) { assertError: assert.NoError, }, "node info returns supported distro ubuntu": { - getOSClient: func(controller *gomock.Controller) nodeinfo.OS { - os := mock_nodeinfo.NewMockOS(controller) + getOSClient: func(controller *gomock.Controller) osutils.Utils { + os := mock_osutils.NewMockUtils(controller) os.EXPECT().GetHostSystemInfo(gomock.Any()).Return(&ubuntuHostSystemResponse, nil) return os }, @@ -104,8 +106,8 @@ func TestNode_GetInfo(t *testing.T) { assertError: assert.NoError, }, "node info returns supported distro amazon linux": { - getOSClient: func(controller *gomock.Controller) nodeinfo.OS { - os := mock_nodeinfo.NewMockOS(controller) + getOSClient: func(controller *gomock.Controller) osutils.Utils { + os := mock_osutils.NewMockUtils(controller) os.EXPECT().GetHostSystemInfo(gomock.Any()).Return(&amazonHostSystemResponse, nil) return os }, @@ -123,8 +125,8 @@ func TestNode_GetInfo(t *testing.T) { assertError: assert.NoError, }, "node info on host with no package manager": { - getOSClient: func(controller *gomock.Controller) nodeinfo.OS { - os := mock_nodeinfo.NewMockOS(controller) + getOSClient: func(controller *gomock.Controller) osutils.Utils { + os := mock_osutils.NewMockUtils(controller) os.EXPECT().GetHostSystemInfo(gomock.Any()).Return(&ubuntuHostSystemResponse, nil) return os }, diff --git a/internal/nodeprep/nodeinfo/utils.go b/internal/nodeprep/nodeinfo/utils.go index 727d7a37a..e95a82c14 100644 --- a/internal/nodeprep/nodeinfo/utils.go +++ b/internal/nodeprep/nodeinfo/utils.go @@ -2,38 +2,19 @@ package nodeinfo -//go:generate mockgen -destination=../../../mocks/mock_internal/mock_nodeprep/mock_nodeinfo/mock_os.go github.com/netapp/trident/internal/nodeprep/nodeinfo OS //go:generate mockgen -destination=../../../mocks/mock_internal/mock_nodeprep/mock_nodeinfo/mock_binary.go github.com/netapp/trident/internal/nodeprep/nodeinfo Binary import ( - "context" - "github.com/netapp/trident/internal/chwrap" - "github.com/netapp/trident/utils" - "github.com/netapp/trident/utils/models" ) // the code in this file need to live under sub packages of utils. // TODO remove this file once the refactoring is done. -type OS interface { - GetHostSystemInfo(ctx context.Context) (*models.HostSystem, error) -} - type Binary interface { FindPath(binaryName string) string } -type OSClient struct{} - -func NewOSClient() *OSClient { - return &OSClient{} -} - -func (o *OSClient) GetHostSystemInfo(ctx context.Context) (*models.HostSystem, error) { - return utils.GetHostSystemInfo(ctx) -} - type BinaryClient struct{} func NewBinary() *BinaryClient { diff --git a/main.go b/main.go index e388ffc4b..3d7c0a7c7 100644 --- a/main.go +++ b/main.go @@ -34,8 +34,8 @@ import ( "github.com/netapp/trident/internal/fiji" . "github.com/netapp/trident/logging" persistentstore "github.com/netapp/trident/persistent_store" - "github.com/netapp/trident/utils" "github.com/netapp/trident/utils/errors" + "github.com/netapp/trident/utils/osutils" ) var ( @@ -304,7 +304,7 @@ func main() { // Process any docker plugin environment variables (after we flag.Parse() the CLI above) if *dockerPluginMode { os.Setenv("DOCKER_PLUGIN_MODE", "1") - utils.SetChrootPathPrefix("/host") + osutils.SetChrootPathPrefix("/host") } err = processDockerPluginArgs(ctx) diff --git a/mocks/mock_utils/mock_iscsi/mock_iscsi_client.go b/mocks/mock_utils/mock_iscsi/mock_iscsi_client.go index e4b9e4a34..0307cb6b2 100644 --- a/mocks/mock_utils/mock_iscsi/mock_iscsi_client.go +++ b/mocks/mock_utils/mock_iscsi/mock_iscsi_client.go @@ -83,6 +83,21 @@ func (mr *MockISCSIMockRecorder) GetDeviceInfoForLUN(arg0, arg1, arg2, arg3, arg return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDeviceInfoForLUN", reflect.TypeOf((*MockISCSI)(nil).GetDeviceInfoForLUN), arg0, arg1, arg2, arg3, arg4) } +// ISCSIActiveOnHost mocks base method. +func (m *MockISCSI) ISCSIActiveOnHost(arg0 context.Context, arg1 models.HostSystem) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ISCSIActiveOnHost", arg0, arg1) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ISCSIActiveOnHost indicates an expected call of ISCSIActiveOnHost. +func (mr *MockISCSIMockRecorder) ISCSIActiveOnHost(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ISCSIActiveOnHost", reflect.TypeOf((*MockISCSI)(nil).ISCSIActiveOnHost), arg0, arg1) +} + // IsAlreadyAttached mocks base method. func (m *MockISCSI) IsAlreadyAttached(arg0 context.Context, arg1 int, arg2 string) bool { m.ctrl.T.Helper() diff --git a/mocks/mock_utils/mock_osutils/mock_netlink.go b/mocks/mock_utils/mock_osutils/mock_netlink.go new file mode 100644 index 000000000..6eba82cc5 --- /dev/null +++ b/mocks/mock_utils/mock_osutils/mock_netlink.go @@ -0,0 +1,100 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/netapp/trident/utils/osutils (interfaces: NetLink) +// +// Generated by this command: +// +// mockgen -destination=../../mocks/mock_utils/mock_osutils/mock_netlink.go github.com/netapp/trident/utils/osutils NetLink +// + +// Package mock_osutils is a generated GoMock package. +package mock_osutils + +import ( + reflect "reflect" + + netlink "github.com/vishvananda/netlink" + gomock "go.uber.org/mock/gomock" +) + +// MockNetLink is a mock of NetLink interface. +type MockNetLink struct { + ctrl *gomock.Controller + recorder *MockNetLinkMockRecorder +} + +// MockNetLinkMockRecorder is the mock recorder for MockNetLink. +type MockNetLinkMockRecorder struct { + mock *MockNetLink +} + +// NewMockNetLink creates a new mock instance. +func NewMockNetLink(ctrl *gomock.Controller) *MockNetLink { + mock := &MockNetLink{ctrl: ctrl} + mock.recorder = &MockNetLinkMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockNetLink) EXPECT() *MockNetLinkMockRecorder { + return m.recorder +} + +// AddrList mocks base method. +func (m *MockNetLink) AddrList(arg0 netlink.Link, arg1 int) ([]netlink.Addr, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddrList", arg0, arg1) + ret0, _ := ret[0].([]netlink.Addr) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AddrList indicates an expected call of AddrList. +func (mr *MockNetLinkMockRecorder) AddrList(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddrList", reflect.TypeOf((*MockNetLink)(nil).AddrList), arg0, arg1) +} + +// LinkByIndex mocks base method. +func (m *MockNetLink) LinkByIndex(arg0 int) (netlink.Link, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LinkByIndex", arg0) + ret0, _ := ret[0].(netlink.Link) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LinkByIndex indicates an expected call of LinkByIndex. +func (mr *MockNetLinkMockRecorder) LinkByIndex(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LinkByIndex", reflect.TypeOf((*MockNetLink)(nil).LinkByIndex), arg0) +} + +// LinkList mocks base method. +func (m *MockNetLink) LinkList() ([]netlink.Link, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LinkList") + ret0, _ := ret[0].([]netlink.Link) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LinkList indicates an expected call of LinkList. +func (mr *MockNetLinkMockRecorder) LinkList() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LinkList", reflect.TypeOf((*MockNetLink)(nil).LinkList)) +} + +// RouteListFiltered mocks base method. +func (m *MockNetLink) RouteListFiltered(arg0 int, arg1 *netlink.Route, arg2 uint64) ([]netlink.Route, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RouteListFiltered", arg0, arg1, arg2) + ret0, _ := ret[0].([]netlink.Route) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// RouteListFiltered indicates an expected call of RouteListFiltered. +func (mr *MockNetLinkMockRecorder) RouteListFiltered(arg0, arg1, arg2 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RouteListFiltered", reflect.TypeOf((*MockNetLink)(nil).RouteListFiltered), arg0, arg1, arg2) +} diff --git a/mocks/mock_utils/mock_osutils/mock_osutils.go b/mocks/mock_utils/mock_osutils/mock_osutils.go new file mode 100644 index 000000000..aca4c396a --- /dev/null +++ b/mocks/mock_utils/mock_osutils/mock_osutils.go @@ -0,0 +1,188 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/netapp/trident/utils/osutils (interfaces: Utils) +// +// Generated by this command: +// +// mockgen -destination=../../mocks/mock_utils/mock_osutils/mock_osutils.go github.com/netapp/trident/utils/osutils Utils +// + +// Package mock_osutils is a generated GoMock package. +package mock_osutils + +import ( + context "context" + reflect "reflect" + time "time" + + models "github.com/netapp/trident/utils/models" + gomock "go.uber.org/mock/gomock" +) + +// MockUtils is a mock of Utils interface. +type MockUtils struct { + ctrl *gomock.Controller + recorder *MockUtilsMockRecorder +} + +// MockUtilsMockRecorder is the mock recorder for MockUtils. +type MockUtilsMockRecorder struct { + mock *MockUtils +} + +// NewMockUtils creates a new mock instance. +func NewMockUtils(ctrl *gomock.Controller) *MockUtils { + mock := &MockUtils{ctrl: ctrl} + mock.recorder = &MockUtilsMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockUtils) EXPECT() *MockUtilsMockRecorder { + return m.recorder +} + +// DeleteResourceAtPath mocks base method. +func (m *MockUtils) DeleteResourceAtPath(arg0 context.Context, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteResourceAtPath", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteResourceAtPath indicates an expected call of DeleteResourceAtPath. +func (mr *MockUtilsMockRecorder) DeleteResourceAtPath(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteResourceAtPath", reflect.TypeOf((*MockUtils)(nil).DeleteResourceAtPath), arg0, arg1) +} + +// EnsureDirExists mocks base method. +func (m *MockUtils) EnsureDirExists(arg0 context.Context, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EnsureDirExists", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// EnsureDirExists indicates an expected call of EnsureDirExists. +func (mr *MockUtilsMockRecorder) EnsureDirExists(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnsureDirExists", reflect.TypeOf((*MockUtils)(nil).EnsureDirExists), arg0, arg1) +} + +// EnsureFileExists mocks base method. +func (m *MockUtils) EnsureFileExists(arg0 context.Context, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EnsureFileExists", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// EnsureFileExists indicates an expected call of EnsureFileExists. +func (mr *MockUtilsMockRecorder) EnsureFileExists(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnsureFileExists", reflect.TypeOf((*MockUtils)(nil).EnsureFileExists), arg0, arg1) +} + +// GetHostSystemInfo mocks base method. +func (m *MockUtils) GetHostSystemInfo(arg0 context.Context) (*models.HostSystem, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetHostSystemInfo", arg0) + ret0, _ := ret[0].(*models.HostSystem) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetHostSystemInfo indicates an expected call of GetHostSystemInfo. +func (mr *MockUtilsMockRecorder) GetHostSystemInfo(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHostSystemInfo", reflect.TypeOf((*MockUtils)(nil).GetHostSystemInfo), arg0) +} + +// GetIPAddresses mocks base method. +func (m *MockUtils) GetIPAddresses(arg0 context.Context) ([]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetIPAddresses", arg0) + ret0, _ := ret[0].([]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetIPAddresses indicates an expected call of GetIPAddresses. +func (mr *MockUtilsMockRecorder) GetIPAddresses(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetIPAddresses", reflect.TypeOf((*MockUtils)(nil).GetIPAddresses), arg0) +} + +// IsLikelyDir mocks base method. +func (m *MockUtils) IsLikelyDir(arg0 string) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsLikelyDir", arg0) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// IsLikelyDir indicates an expected call of IsLikelyDir. +func (mr *MockUtilsMockRecorder) IsLikelyDir(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsLikelyDir", reflect.TypeOf((*MockUtils)(nil).IsLikelyDir), arg0) +} + +// NFSActiveOnHost mocks base method. +func (m *MockUtils) NFSActiveOnHost(arg0 context.Context) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NFSActiveOnHost", arg0) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NFSActiveOnHost indicates an expected call of NFSActiveOnHost. +func (mr *MockUtilsMockRecorder) NFSActiveOnHost(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NFSActiveOnHost", reflect.TypeOf((*MockUtils)(nil).NFSActiveOnHost), arg0) +} + +// PathExists mocks base method. +func (m *MockUtils) PathExists(arg0 string) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PathExists", arg0) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PathExists indicates an expected call of PathExists. +func (mr *MockUtilsMockRecorder) PathExists(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PathExists", reflect.TypeOf((*MockUtils)(nil).PathExists), arg0) +} + +// ServiceActiveOnHost mocks base method. +func (m *MockUtils) ServiceActiveOnHost(arg0 context.Context, arg1 string) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ServiceActiveOnHost", arg0, arg1) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ServiceActiveOnHost indicates an expected call of ServiceActiveOnHost. +func (mr *MockUtilsMockRecorder) ServiceActiveOnHost(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ServiceActiveOnHost", reflect.TypeOf((*MockUtils)(nil).ServiceActiveOnHost), arg0, arg1) +} + +// WaitForResourceDeletionAtPath mocks base method. +func (m *MockUtils) WaitForResourceDeletionAtPath(arg0 context.Context, arg1 string, arg2 time.Duration) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WaitForResourceDeletionAtPath", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// WaitForResourceDeletionAtPath indicates an expected call of WaitForResourceDeletionAtPath. +func (mr *MockUtilsMockRecorder) WaitForResourceDeletionAtPath(arg0, arg1, arg2 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitForResourceDeletionAtPath", reflect.TypeOf((*MockUtils)(nil).WaitForResourceDeletionAtPath), arg0, arg1, arg2) +} diff --git a/storage_drivers/ontap/ontap_san.go b/storage_drivers/ontap/ontap_san.go index be0202d91..ee91d6d5a 100644 --- a/storage_drivers/ontap/ontap_san.go +++ b/storage_drivers/ontap/ontap_san.go @@ -98,7 +98,7 @@ func (d *SANStorageDriver) Initialize( // Initialize the iSCSI client var err error - d.iscsi, err = iscsi.New(utils.NewOSClient()) + d.iscsi, err = iscsi.New() 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 0661220c5..24049679e 100644 --- a/storage_drivers/ontap/ontap_san_economy.go +++ b/storage_drivers/ontap/ontap_san_economy.go @@ -253,7 +253,7 @@ func (d *SANEconomyStorageDriver) Initialize( // Initialize the iSCSI client var err error - d.iscsi, err = iscsi.New(utils.NewOSClient()) + d.iscsi, err = iscsi.New() 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 90a0945db..10e52f623 100644 --- a/storage_drivers/ontap/ontap_san_economy_test.go +++ b/storage_drivers/ontap/ontap_san_economy_test.go @@ -196,7 +196,7 @@ func newTestOntapSanEcoDriver(t *testing.T, vserverAdminHost, vserverAdminPort, config.AWSConfig.FSxFilesystemID = *fsxId } - iscsiClient, err := iscsi.New(utils.NewOSClient()) + iscsiClient, err := iscsi.New() assert.NoError(t, err) sanEcoDriver := &SANEconomyStorageDriver{ diff --git a/storage_drivers/ontap/ontap_san_test.go b/storage_drivers/ontap/ontap_san_test.go index ce118d554..6afe470f8 100644 --- a/storage_drivers/ontap/ontap_san_test.go +++ b/storage_drivers/ontap/ontap_san_test.go @@ -22,7 +22,6 @@ import ( drivers "github.com/netapp/trident/storage_drivers" "github.com/netapp/trident/storage_drivers/ontap/api" "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" @@ -71,7 +70,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()) + iscsiClient, err := iscsi.New() assert.NoError(t, err) driver.iscsi = iscsiClient diff --git a/storage_drivers/solidfire/solidfire_san.go b/storage_drivers/solidfire/solidfire_san.go index e5abd87ed..0467550a0 100644 --- a/storage_drivers/solidfire/solidfire_san.go +++ b/storage_drivers/solidfire/solidfire_san.go @@ -145,7 +145,7 @@ func (d *SANStorageDriver) Initialize( // Initialize the iSCSI client var err error - d.iscsi, err = iscsi.New(utils.NewOSClient()) + d.iscsi, err = iscsi.New() if err != nil { return fmt.Errorf("could not initialize iSCSI client: %v", err) } diff --git a/utils/adaptors.go b/utils/adaptors.go index f4c365139..3a4446905 100644 --- a/utils/adaptors.go +++ b/utils/adaptors.go @@ -10,16 +10,6 @@ import ( // TODO (vivintw) this file needs to be removed once all the files under utils are packaged correctly. -type OSClient struct{} - -func NewOSClient() OSClient { - return OSClient{} -} - -func (c OSClient) PathExists(path string) (bool, error) { - return PathExists(path) -} - func GetDeviceInfoForLUN( ctx context.Context, hostSessionMap map[int]int, lunID int, iSCSINodeName string, isDetachCall bool, ) (*models.ScsiDeviceInfo, error) { diff --git a/utils/fcp.go b/utils/fcp.go index 3f0927758..8d6a7eac4 100644 --- a/utils/fcp.go +++ b/utils/fcp.go @@ -1,12 +1,16 @@ package utils import ( + "github.com/netapp/trident/utils/devices" + "github.com/netapp/trident/utils/exec" "github.com/netapp/trident/utils/fcp" "github.com/netapp/trident/utils/filesystem" + "github.com/netapp/trident/utils/osutils" ) var ( - FcpUtils = fcp.NewReconcileUtils(chrootPathPrefix, NewOSClient()) - fcpClient = fcp.NewDetailed(chrootPathPrefix, command, fcp.DefaultSelfHealingExclusion, NewOSClient(), - devicesClient, filesystem.New(mountClient), mountClient, FcpUtils) + command = exec.NewCommand() + FcpUtils = fcp.NewReconcileUtils(osutils.ChrootPathPrefix, osutils.New()) + fcpClient = fcp.NewDetailed(osutils.ChrootPathPrefix, command, fcp.DefaultSelfHealingExclusion, osutils.New(), + devices.New(), filesystem.New(mountClient), mountClient, FcpUtils) ) diff --git a/utils/fcp/fcp.go b/utils/fcp/fcp.go index e17569e8b..0dde1ddfd 100644 --- a/utils/fcp/fcp.go +++ b/utils/fcp/fcp.go @@ -24,6 +24,7 @@ import ( "github.com/netapp/trident/utils/filesystem" "github.com/netapp/trident/utils/models" "github.com/netapp/trident/utils/mount" + "github.com/netapp/trident/utils/osutils" ) const ( @@ -96,12 +97,10 @@ type Client struct { fcpUtils FcpReconcileUtils } -func New(osClient OS, fileSystemClient FileSystem) (*Client, error) { - chrootPathPrefix := "" - if os.Getenv("DOCKER_PLUGIN_MODE") != "" { - chrootPathPrefix = "/host" - } +func New() (*Client, error) { + chrootPathPrefix := osutils.ChrootPathPrefix + osClient := osutils.New() reconcileutils := NewReconcileUtils(chrootPathPrefix, osClient) mountClient, err := mount.New() @@ -109,6 +108,8 @@ func New(osClient OS, fileSystemClient FileSystem) (*Client, error) { return nil, err } + fileSystemClient := filesystem.New(mountClient) + return NewDetailed(chrootPathPrefix, tridentexec.NewCommand(), DefaultSelfHealingExclusion, osClient, devices.New(), fileSystemClient, mountClient, reconcileutils), nil } diff --git a/utils/iscsi.go b/utils/iscsi.go index e4a032dda..9b5244c13 100644 --- a/utils/iscsi.go +++ b/utils/iscsi.go @@ -25,6 +25,7 @@ import ( "github.com/netapp/trident/utils/iscsi" "github.com/netapp/trident/utils/models" "github.com/netapp/trident/utils/mount" + "github.com/netapp/trident/utils/osutils" ) const ( @@ -34,11 +35,12 @@ const ( ) var ( + iqnRegex = regexp.MustCompile(`^\s*InitiatorName\s*=\s*(?P\S+)(|\s+.*)$`) mountClient, _ = mount.New() - IscsiUtils = iscsi.NewReconcileUtils(chrootPathPrefix, NewOSClient()) + IscsiUtils = iscsi.NewReconcileUtils(osutils.ChrootPathPrefix, osutils.New()) devicesClient = devices.New() - iscsiClient = iscsi.NewDetailed(chrootPathPrefix, command, iscsi.DefaultSelfHealingExclusion, NewOSClient(), - devicesClient, filesystem.New(mountClient), mountClient, IscsiUtils, afero.Afero{Fs: afero.NewOsFs()}) + iscsiClient = iscsi.NewDetailed(osutils.ChrootPathPrefix, command, iscsi.DefaultSelfHealingExclusion, osutils.New(), + devicesClient, filesystem.New(mountClient), mountClient, IscsiUtils, afero.Afero{Fs: afero.NewOsFs()}, nil) // perNodeIgroupRegex is used to ensure an igroup meets the following format: // -<36 characters of trident version uuid> diff --git a/utils/iscsi/iscsi.go b/utils/iscsi/iscsi.go index 222d16134..9569d8bbd 100644 --- a/utils/iscsi/iscsi.go +++ b/utils/iscsi/iscsi.go @@ -30,6 +30,7 @@ import ( "github.com/netapp/trident/utils/filesystem" "github.com/netapp/trident/utils/models" "github.com/netapp/trident/utils/mount" + "github.com/netapp/trident/utils/osutils" ) const ( @@ -80,6 +81,7 @@ type ISCSI interface { TargetHasMountedDevice(ctx context.Context, targetIQN string) (bool, error) SafeToLogOut(ctx context.Context, hostNumber, sessionNumber int) bool Logout(ctx context.Context, targetIQN, targetPortal string) error + ISCSIActiveOnHost(ctx context.Context, host models.HostSystem) (bool, error) GetDeviceInfoForLUN( ctx context.Context, hostSessionMap map[int]int, lunID int, iSCSINodeName string, needFSType bool, ) (*models.ScsiDeviceInfo, error) @@ -143,16 +145,15 @@ type Client struct { mountClient mount.Mount iscsiUtils IscsiReconcileUtils os afero.Afero + osUtils osutils.Utils } -func New(osClient OS) (*Client, error) { - chrootPathPrefix := "" - if os.Getenv("DOCKER_PLUGIN_MODE") != "" { - chrootPathPrefix = "/host" - } +func New() (*Client, error) { + chrootPathPrefix := osutils.ChrootPathPrefix - reconcileutils := NewReconcileUtils(chrootPathPrefix, osClient) - osUtils := afero.Afero{Fs: afero.NewOsFs()} + osUtils := osutils.New() + reconcileutils := NewReconcileUtils(chrootPathPrefix, osUtils) + osFs := afero.Afero{Fs: afero.NewOsFs()} mountClient, err := mount.New() if err != nil { return nil, fmt.Errorf("error creating mount client: %v", err) @@ -161,14 +162,14 @@ func New(osClient OS) (*Client, error) { fsClient := filesystem.New(mountClient) devicesClient := devices.New() - return NewDetailed(chrootPathPrefix, tridentexec.NewCommand(), DefaultSelfHealingExclusion, osClient, - devicesClient, fsClient, mountClient, reconcileutils, osUtils), nil + return NewDetailed(chrootPathPrefix, tridentexec.NewCommand(), DefaultSelfHealingExclusion, osUtils, + devicesClient, fsClient, mountClient, reconcileutils, osFs, osUtils), nil } func NewDetailed(chrootPathPrefix string, command tridentexec.Command, selfHealingExclusion []string, osClient OS, devices devices.Devices, fileSystemClient filesystem.Filesystem, mountClient mount.Mount, iscsiUtils IscsiReconcileUtils, - os afero.Afero, + os afero.Afero, osUtils osutils.Utils, ) *Client { return &Client{ chrootPathPrefix: chrootPathPrefix, @@ -180,6 +181,7 @@ func NewDetailed(chrootPathPrefix string, command tridentexec.Command, selfHeali iscsiUtils: iscsiUtils, selfHealingExclusion: selfHealingExclusion, os: os, + osUtils: osUtils, } } diff --git a/utils/iscsi_darwin.go b/utils/iscsi/iscsi_darwin.go similarity index 82% rename from utils/iscsi_darwin.go rename to utils/iscsi/iscsi_darwin.go index 504a8e480..6cd90d11d 100644 --- a/utils/iscsi_darwin.go +++ b/utils/iscsi/iscsi_darwin.go @@ -2,7 +2,7 @@ // File to contain iscsi protocol related functionalities for Darwin flavor -package utils +package iscsi import ( "context" @@ -13,7 +13,7 @@ import ( ) // ISCSIActiveOnHost unused stub function -func ISCSIActiveOnHost(ctx context.Context, host models.HostSystem) (bool, error) { +func (client *Client) ISCSIActiveOnHost(ctx context.Context, host models.HostSystem) (bool, error) { Logc(ctx).Debug(">>>> iscsi_darwin.ISCSIActiveOnHost") defer Logc(ctx).Debug("<<<< iscsi_darwin.ISCSIActiveOnHost") return false, errors.UnsupportedError("ISCSIActiveOnHost is not supported for darwin") diff --git a/utils/iscsi_darwin_test.go b/utils/iscsi/iscsi_darwin_test.go similarity index 82% rename from utils/iscsi_darwin_test.go rename to utils/iscsi/iscsi_darwin_test.go index 97dc85cdf..2c5529a8d 100644 --- a/utils/iscsi_darwin_test.go +++ b/utils/iscsi/iscsi_darwin_test.go @@ -1,6 +1,6 @@ // Copyright 2022 NetApp, Inc. All Rights Reserved. -package utils +package iscsi import ( "context" @@ -21,7 +21,9 @@ func TestISCSIActiveOnHost(t *testing.T) { Services: []string{"srv-1", "srv-2"}, } - result, err := ISCSIActiveOnHost(ctx, host) + iscsiClient, err := New() + assert.NoError(t, err) + result, err := iscsiClient.ISCSIActiveOnHost(ctx, host) assert.False(t, result, "iscsi is active on host") assert.Error(t, err, "no error") assert.True(t, errors.IsUnsupportedError(err), "not UnsupportedError") diff --git a/utils/iscsi_linux.go b/utils/iscsi/iscsi_linux.go similarity index 57% rename from utils/iscsi_linux.go rename to utils/iscsi/iscsi_linux.go index 9e2cfd445..b72540b27 100644 --- a/utils/iscsi_linux.go +++ b/utils/iscsi/iscsi_linux.go @@ -1,29 +1,28 @@ -// Copyright 2022 NetApp, Inc. All Rights Reserved. +// Copyright 2024 NetApp, Inc. All Rights Reserved. -// File to contain iscsi protocol related functionalities for linux flavor - -package utils +package iscsi import ( "context" . "github.com/netapp/trident/logging" "github.com/netapp/trident/utils/models" + "github.com/netapp/trident/utils/osutils" ) // ISCSIActiveOnHost will return if the iscsi daemon is active on the given host -func ISCSIActiveOnHost(ctx context.Context, host models.HostSystem) (bool, error) { +func (client *Client) ISCSIActiveOnHost(ctx context.Context, host models.HostSystem) (bool, error) { Logc(ctx).Debug(">>>> iscsi_linux.ISCSIActiveOnHost") defer Logc(ctx).Debug("<<<< iscsi_linux.ISCSIActiveOnHost") var serviceName string switch host.OS.Distro { - case Ubuntu, Debian: + case osutils.Ubuntu, osutils.Debian: serviceName = "open-iscsi" default: serviceName = "iscsid" } - return ServiceActiveOnHost(ctx, serviceName) + return client.osUtils.ServiceActiveOnHost(ctx, serviceName) } diff --git a/utils/iscsi/iscsi_linux_test.go b/utils/iscsi/iscsi_linux_test.go index 205cfdb19..25444bda9 100644 --- a/utils/iscsi/iscsi_linux_test.go +++ b/utils/iscsi/iscsi_linux_test.go @@ -23,6 +23,7 @@ import ( "github.com/netapp/trident/utils/filesystem" "github.com/netapp/trident/utils/models" "github.com/netapp/trident/utils/mount" + "github.com/netapp/trident/utils/osutils" ) func TestClient_AttachVolume_LUKS(t *testing.T) { @@ -258,7 +259,8 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` ctrl := gomock.NewController(t) iscsiClient := NewDetailed(params.chrootPathPrefix, params.getCommand(ctrl), DefaultSelfHealingExclusion, params.getOSClient(ctrl), params.getDeviceClient(ctrl), params.getFileSystemClient(ctrl), - params.getMountClient(ctrl), params.getReconcileUtils(ctrl), afero.Afero{Fs: params.getFileSystemUtils()}) + params.getMountClient(ctrl), params.getReconcileUtils(ctrl), + afero.Afero{Fs: params.getFileSystemUtils()}, nil) mpathSize, err := iscsiClient.AttachVolume(context.TODO(), params.volumeName, params.volumeMountPoint, ¶ms.publishInfo, params.volumeAuthSecrets) @@ -270,3 +272,98 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` }) } } + +func TestISCSIActiveOnHost(t *testing.T) { + mockCtrl := gomock.NewController(t) + mockExec := mockexec.NewMockCommand(mockCtrl) + + type args struct { + ctx context.Context + host models.HostSystem + } + type expected struct { + active bool + err error + } + tests := map[string]struct { + name string + args args + expected expected + service string + }{ + "ActiveCentos": { + args: args{ + ctx: context.Background(), + host: models.HostSystem{OS: models.SystemOS{Distro: osutils.Centos}}, + }, + expected: expected{ + active: true, + err: nil, + }, + service: "iscsid", + }, + "ActiveRHEL": { + args: args{ + ctx: context.Background(), + host: models.HostSystem{OS: models.SystemOS{Distro: osutils.RHEL}}, + }, + expected: expected{ + active: true, + err: nil, + }, + service: "iscsid", + }, + "ActiveUbuntu": { + args: args{ + ctx: context.Background(), + host: models.HostSystem{OS: models.SystemOS{Distro: osutils.Ubuntu}}, + }, + expected: expected{ + active: true, + err: nil, + }, + service: "open-iscsi", + }, + "InactiveRHEL": { + args: args{ + ctx: context.Background(), + host: models.HostSystem{OS: models.SystemOS{Distro: osutils.RHEL}}, + }, + expected: expected{ + active: false, + err: nil, + }, + service: "iscsid", + }, + "UnknownDistro": { + args: args{ + ctx: context.Background(), + host: models.HostSystem{OS: models.SystemOS{Distro: "SUSE"}}, + }, + expected: expected{ + active: true, + err: nil, + }, + service: "iscsid", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockExec.EXPECT().ExecuteWithTimeout( + tt.args.ctx, "systemctl", 30*time.Second, true, "is-active", tt.service, + ).Return([]byte(""), tt.expected.err) + + osUtils := osutils.NewDetailed(mockExec, afero.NewMemMapFs()) + iscsiClient := NewDetailed("", mockExec, nil, nil, nil, nil, nil, nil, afero.Afero{Fs: afero.NewMemMapFs()}, osUtils) + active, err := iscsiClient.ISCSIActiveOnHost(tt.args.ctx, tt.args.host) + if tt.expected.err != nil { + assert.Error(t, err) + assert.False(t, active) + } else { + assert.NoError(t, err) + assert.True(t, active) + } + }) + } +} diff --git a/utils/iscsi/iscsi_test.go b/utils/iscsi/iscsi_test.go index da51e9851..3b87668ba 100644 --- a/utils/iscsi/iscsi_test.go +++ b/utils/iscsi/iscsi_test.go @@ -31,9 +31,6 @@ import ( ) func TestNew(t *testing.T) { - ctrl := gomock.NewController(t) - osClient := mock_iscsi.NewMockOS(ctrl) - type parameters struct { setUpEnvironment func() } @@ -52,7 +49,7 @@ func TestNew(t *testing.T) { params.setUpEnvironment() } - iscsiClient, err := New(osClient) + iscsiClient, err := New() assert.NoError(t, err) assert.NotNil(t, iscsiClient) }) @@ -68,7 +65,7 @@ func TestNewDetailed(t *testing.T) { mountClient := mock_mount.NewMockMount(ctrl) command := mockexec.NewMockCommand(ctrl) iscsiClient := NewDetailed(chrootPathPrefix, command, DefaultSelfHealingExclusion, osClient, devicesClient, FileSystemClient, - mountClient, nil, afero.Afero{Fs: afero.NewMemMapFs()}) + mountClient, nil, afero.Afero{Fs: afero.NewMemMapFs()}, nil) assert.NotNil(t, iscsiClient) } @@ -264,7 +261,8 @@ tcp: [4] 127.0.0.1:3260,1029 iqn.2016-04.com.open-iscsi:ef9f41e2ffa7:vs.3 (non-f ctrl := gomock.NewController(t) iscsiClient := NewDetailed(params.chrootPathPrefix, params.getCommand(ctrl), DefaultSelfHealingExclusion, params.getOSClient(ctrl), params.getDeviceClient(ctrl), params.getFileSystemClient(ctrl), - params.getMountClient(ctrl), params.getReconcileUtils(ctrl), afero.Afero{Fs: params.getFileSystemUtils()}) + params.getMountClient(ctrl), params.getReconcileUtils(ctrl), + afero.Afero{Fs: params.getFileSystemUtils()}, nil) mpathSize, err := iscsiClient.AttachVolumeRetry(context.TODO(), "", "", ¶ms.publishInfo, nil, testTimeout) @@ -2170,7 +2168,8 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` ctrl := gomock.NewController(t) iscsiClient := NewDetailed(params.chrootPathPrefix, params.getCommand(ctrl), DefaultSelfHealingExclusion, params.getOSClient(ctrl), params.getDeviceClient(ctrl), params.getFileSystemClient(ctrl), - params.getMountClient(ctrl), params.getReconcileUtils(ctrl), afero.Afero{Fs: params.getFileSystemUtils()}) + params.getMountClient(ctrl), params.getReconcileUtils(ctrl), + afero.Afero{Fs: params.getFileSystemUtils()}, nil) mpathSize, err := iscsiClient.AttachVolume(context.TODO(), params.volumeName, params.volumeMountPoint, ¶ms.publishInfo, params.volumeAuthSecrets) @@ -2243,7 +2242,7 @@ func TestClient_AddSession(t *testing.T) { for name, params := range tests { t.Run(name, func(t *testing.T) { - client, err := New(nil) + client, err := New() assert.NoError(t, err) ctx := context.WithValue(context.TODO(), SessionInfoSource, "test") client.AddSession(ctx, params.sessions, ¶ms.publishInfo, params.volID, @@ -2686,7 +2685,8 @@ func TestClient_RescanDevices(t *testing.T) { controller := gomock.NewController(t) client := NewDetailed("", params.getCommandClient(controller), DefaultSelfHealingExclusion, nil, - params.getDeviceClient(controller), nil, nil, params.getReconcileUtils(controller), afero.Afero{Fs: params.getFileSystemUtils()}) + params.getDeviceClient(controller), nil, nil, params.getReconcileUtils(controller), + afero.Afero{Fs: params.getFileSystemUtils()}, nil) err := client.RescanDevices(context.TODO(), params.targetIQN, params.lunID, params.minSize) if params.assertError != nil { @@ -2790,7 +2790,8 @@ func TestClient_rescanDisk(t *testing.T) { if params.getDevices != nil { deviceClient = params.getDevices(gomock.NewController(t)) } - client := NewDetailed("", nil, nil, nil, deviceClient, nil, nil, nil, afero.Afero{Fs: params.getFileSystemUtils()}) + client := NewDetailed("", nil, nil, nil, deviceClient, nil, nil, nil, + afero.Afero{Fs: params.getFileSystemUtils()}, nil) err := client.rescanDisk(context.TODO(), deviceName) if params.assertError != nil { params.assertError(t, err) @@ -2843,7 +2844,7 @@ func TestClient_reloadMultipathDevice(t *testing.T) { for name, params := range tests { t.Run(name, func(t *testing.T) { ctrl := gomock.NewController(t) - client := NewDetailed("", params.getCommand(ctrl), nil, nil, nil, nil, nil, nil, afero.Afero{}) + client := NewDetailed("", params.getCommand(ctrl), nil, nil, nil, nil, nil, nil, afero.Afero{}, nil) err := client.reloadMultipathDevice(context.TODO(), params.multipathDeviceName) if params.assertError != nil { @@ -2918,7 +2919,7 @@ func TestClient_IsAlreadyAttached(t *testing.T) { for name, params := range tests { t.Run(name, func(t *testing.T) { controller := gomock.NewController(t) - client := NewDetailed("", nil, nil, nil, nil, nil, nil, params.getIscsiUtils(controller), afero.Afero{}) + client := NewDetailed("", nil, nil, nil, nil, nil, nil, params.getIscsiUtils(controller), afero.Afero{}, nil) attached := client.IsAlreadyAttached(context.TODO(), params.lunID, params.targetIQN) if params.assertResponse != nil { @@ -3126,7 +3127,7 @@ func TestClient_getDeviceInfoForLUN(t *testing.T) { params.getIscsiUtils(ctrl), afero.Afero{ Fs: params.getFileSystemUtils(), - }) + }, nil) deviceInfo, err := client.GetDeviceInfoForLUN(context.TODO(), params.hostSessionMap, params.lunID, params.iSCSINodeName, params.needFS) if params.assertError != nil { @@ -3198,7 +3199,7 @@ func TestClient_purgeOneLun(t *testing.T) { for name, params := range tests { t.Run(name, func(t *testing.T) { - client := NewDetailed("", nil, nil, nil, nil, nil, nil, nil, afero.Afero{Fs: params.getFileSystemUtils()}) + client := NewDetailed("", nil, nil, nil, nil, nil, nil, nil, afero.Afero{Fs: params.getFileSystemUtils()}, nil) err := client.purgeOneLun(context.TODO(), params.path) if params.assertError != nil { params.assertError(t, err) @@ -3278,7 +3279,7 @@ func TestClient_rescanOneLun(t *testing.T) { for name, params := range tests { t.Run(name, func(t *testing.T) { - client := NewDetailed("", nil, nil, nil, nil, nil, nil, nil, afero.Afero{Fs: params.getFileSystemUtils()}) + client := NewDetailed("", nil, nil, nil, nil, nil, nil, nil, afero.Afero{Fs: params.getFileSystemUtils()}, nil) err := client.rescanOneLun(context.TODO(), params.path) if params.assertError != nil { @@ -3375,7 +3376,8 @@ func TestClient_waitForMultipathDeviceForLUN(t *testing.T) { if params.getDevices != nil { deviceClient = params.getDevices(ctrl) } - client := NewDetailed("", nil, nil, nil, deviceClient, nil, nil, params.getIscsiUtils(ctrl), afero.Afero{Fs: params.getFileSystemUtils()}) + client := NewDetailed("", nil, nil, nil, deviceClient, nil, nil, params.getIscsiUtils(ctrl), + afero.Afero{Fs: params.getFileSystemUtils()}, nil) err := client.waitForMultipathDeviceForLUN(context.TODO(), params.hostSessionMap, lunID, iscsiNodeName) if params.assertError != nil { @@ -3524,7 +3526,7 @@ func TestClient_waitForDeviceScan(t *testing.T) { } client := NewDetailed("", params.getCommandClient(ctrl), nil, params.getOsClient(ctrl), deviceClient, nil, nil, params.getIscsiUtils(ctrl), - afero.Afero{Fs: params.getFileSystemUtils()}) + afero.Afero{Fs: params.getFileSystemUtils()}, nil) err := client.waitForDeviceScan(context.TODO(), params.hostSessionMap, lunID, iscsiNodeName) if params.assertError != nil { @@ -3670,7 +3672,8 @@ func TestClient_handleInvalidSerials(t *testing.T) { devicesClient = params.getDevicesClient(ctrl) } - client := NewDetailed("", nil, nil, nil, devicesClient, nil, nil, params.getIscsiUtils(ctrl), afero.Afero{Fs: params.getFileSystemUtils()}) + client := NewDetailed("", nil, nil, nil, devicesClient, nil, nil, params.getIscsiUtils(ctrl), + afero.Afero{Fs: params.getFileSystemUtils()}, nil) err := client.handleInvalidSerials(context.TODO(), params.hostSessionMap, lunID, targetIQN, params.expectedSerial, mockHandler) if params.assertError != nil { @@ -3767,7 +3770,7 @@ tcp: [4] 127.0.0.2:3260,1029 ` + alternateTargetIQN + ` (non-flash)` t.Run(name, func(t *testing.T) { ctrl := gomock.NewController(t) client := NewDetailed("", params.getCommandClient(ctrl), nil, nil, nil, nil, nil, nil, - afero.Afero{Fs: params.getFileSystemUtils()}) + afero.Afero{Fs: params.getFileSystemUtils()}, nil) portalsNeedingLogin, loggedIn, err := client.portalsToLogin(context.TODO(), targetIQN, []string{ portal1, @@ -3849,7 +3852,7 @@ func TestClient_verifyMultipathDeviceSerial(t *testing.T) { if params.getDevices != nil { devicesClient = params.getDevices(ctrl) } - client := NewDetailed("", nil, nil, nil, devicesClient, nil, nil, nil, afero.Afero{}) + client := NewDetailed("", nil, nil, nil, devicesClient, nil, nil, nil, afero.Afero{}, nil) err := client.verifyMultipathDeviceSerial(context.TODO(), multipathDeviceName, params.lunSerial) if params.assertError != nil { params.assertError(t, err) @@ -4115,7 +4118,7 @@ tcp: [4] 127.0.0.2:3260,1029 ` + targetIQN + ` (non-flash)` devicesClient = params.getDevices(ctrl) } client := NewDetailed("", params.getCommandClient(ctrl), nil, nil, devicesClient, nil, nil, nil, - afero.Afero{Fs: params.getFileSystemUtils()}) + afero.Afero{Fs: params.getFileSystemUtils()}, nil) successfullyLoggedIn, err := client.EnsureSessions(context.TODO(), ¶ms.publishInfo, params.portals) if params.assertError != nil { params.assertError(t, err) @@ -4269,7 +4272,7 @@ func TestClient_LoginTarget(t *testing.T) { deviceClient = params.getDeviceClient(ctrl) } iscsiClient := NewDetailed("", params.getCommandClient(ctrl), nil, nil, deviceClient, nil, nil, nil, - afero.Afero{Fs: afero.NewMemMapFs()}) + afero.Afero{Fs: afero.NewMemMapFs()}, nil) err := iscsiClient.LoginTarget(context.TODO(), ¶ms.publishInfo, params.portal) if params.assertError != nil { @@ -4465,7 +4468,7 @@ func TestClient_ensureTarget(t *testing.T) { for name, params := range tests { t.Run(name, func(t *testing.T) { ctrl := gomock.NewController(t) - client := NewDetailed("", params.getCommandClient(ctrl), nil, nil, nil, nil, nil, nil, afero.Afero{}) + client := NewDetailed("", params.getCommandClient(ctrl), nil, nil, nil, nil, nil, nil, afero.Afero{}, nil) err := client.ensureTarget(context.TODO(), targetPortal, targetIQN, params.username, params.password, params.targetUsername, params.targetInitiatorSecret, networkInterface) if params.assertError != nil { @@ -4504,7 +4507,7 @@ func TestClient_multipathdIsRunning(t *testing.T) { ctx, gomock.Any(), gomock.Any(), gomock.Any(), ).Return([]byte(tt.execOut), tt.execErr) } - iscsiClient := NewDetailed("", mockExec, nil, nil, nil, nil, nil, nil, afero.Afero{}) + iscsiClient := NewDetailed("", mockExec, nil, nil, nil, nil, nil, nil, afero.Afero{}, nil) actualValue := iscsiClient.multipathdIsRunning(context.Background()) assert.Equal(t, tt.expectedValue, actualValue) diff --git a/utils/iscsi_windows.go b/utils/iscsi/iscsi_windows.go similarity index 62% rename from utils/iscsi_windows.go rename to utils/iscsi/iscsi_windows.go index e9f26c319..13acf1176 100644 --- a/utils/iscsi_windows.go +++ b/utils/iscsi/iscsi_windows.go @@ -1,8 +1,6 @@ -// Copyright 2022 NetApp, Inc. All Rights Reserved. +// Copyright 2024 NetApp, Inc. All Rights Reserved. -// This file should only contain functions for handling the filesystem for Windows flavor - -package utils +package iscsi import ( "context" @@ -13,7 +11,7 @@ import ( ) // ISCSIActiveOnHost unused stub function -func ISCSIActiveOnHost(ctx context.Context, host models.HostSystem) (bool, error) { +func (client *Client) ISCSIActiveOnHost(ctx context.Context, host models.HostSystem) (bool, error) { Logc(ctx).Debug(">>>> iscsi_windows.ISCSIActiveOnHost") defer Logc(ctx).Debug("<<<< iscsi_windows.ISCSIActiveOnHost") return false, errors.UnsupportedError("ISCSIActiveOnHost is not supported for windows") diff --git a/utils/iscsi_windows_test.go b/utils/iscsi/iscsi_windows_test.go similarity index 73% rename from utils/iscsi_windows_test.go rename to utils/iscsi/iscsi_windows_test.go index a4e8b4e1d..4144ce006 100644 --- a/utils/iscsi_windows_test.go +++ b/utils/iscsi/iscsi_windows_test.go @@ -1,11 +1,12 @@ // Copyright 2022 NetApp, Inc. All Rights Reserved. -package utils +package iscsi import ( "context" "testing" + "github.com/spf13/afero" "github.com/stretchr/testify/assert" "github.com/netapp/trident/utils/errors" @@ -21,7 +22,8 @@ func TestISCSIActiveOnHost(t *testing.T) { Services: []string{"srv-1", "srv-2"}, } - result, err := ISCSIActiveOnHost(ctx, host) + iscsiClient := NewDetailed("", nil, nil, nil, nil, nil, nil, nil, afero.Afero{Fs: afero.NewMemMapFs()}, nil) + result, err := iscsiClient.ISCSIActiveOnHost(ctx, host) assert.False(t, result, "iscsi is active on host") assert.Error(t, err, "no error") assert.True(t, errors.IsUnsupportedError(err), "not UnsupportedError") diff --git a/utils/osutils/netlink_linux.go b/utils/osutils/netlink_linux.go new file mode 100644 index 000000000..73c0b1066 --- /dev/null +++ b/utils/osutils/netlink_linux.go @@ -0,0 +1,36 @@ +// Copyright 2024 NetApp, Inc. All Rights Reserved. + +package osutils + +import "github.com/vishvananda/netlink" + +var netLink NetLink = NewNetLinkClient() + +type NetLink interface { + LinkList() ([]netlink.Link, error) + AddrList(link netlink.Link, family int) ([]netlink.Addr, error) + RouteListFiltered(family int, filter *netlink.Route, filterMask uint64) ([]netlink.Route, error) + LinkByIndex(index int) (netlink.Link, error) +} + +type NetLinkClient struct{} + +func NewNetLinkClient() *NetLinkClient { + return &NetLinkClient{} +} + +func (n *NetLinkClient) LinkList() ([]netlink.Link, error) { + return netlink.LinkList() +} + +func (n *NetLinkClient) AddrList(link netlink.Link, family int) ([]netlink.Addr, error) { + return netlink.AddrList(link, family) +} + +func (n *NetLinkClient) RouteListFiltered(family int, filter *netlink.Route, filterMask uint64) ([]netlink.Route, error) { + return netlink.RouteListFiltered(family, filter, filterMask) +} + +func (n *NetLinkClient) LinkByIndex(index int) (netlink.Link, error) { + return netlink.LinkByIndex(index) +} diff --git a/utils/osutils.go b/utils/osutils/osutils.go similarity index 65% rename from utils/osutils.go rename to utils/osutils/osutils.go index 6fd20e599..76f4bd129 100644 --- a/utils/osutils.go +++ b/utils/osutils/osutils.go @@ -1,33 +1,65 @@ // Copyright 2024 NetApp, Inc. All Rights Reserved. -package utils +package osutils + +//go:generate mockgen -destination=../../mocks/mock_utils/mock_osutils/mock_osutils.go github.com/netapp/trident/utils/osutils Utils +//go:generate mockgen -destination=../../mocks/mock_utils/mock_osutils/mock_netlink.go github.com/netapp/trident/utils/osutils NetLink import ( "context" "fmt" "net" "os" - "regexp" "sort" "strings" "time" "github.com/cenkalti/backoff/v4" + "github.com/spf13/afero" . "github.com/netapp/trident/logging" "github.com/netapp/trident/utils/exec" + "github.com/netapp/trident/utils/models" ) -var ( - iqnRegex = regexp.MustCompile(`^\s*InitiatorName\s*=\s*(?P\S+)(|\s+.*)$`) - xtermControlRegex = regexp.MustCompile(`\x1B\[[0-9;]*[a-zA-Z]`) - deviceRegex = regexp.MustCompile(`/dev/(?P[\w-]+)`) - - chrootPathPrefix string - // FIXME: Instead of a package-level variable, pass command into other utils once their interfaces are defined. - command = exec.NewCommand() +const ( + Centos = "centos" + RHEL = "rhel" + Ubuntu = "ubuntu" + Debian = "debian" ) +var ChrootPathPrefix string + +type Utils interface { + NFSActiveOnHost(ctx context.Context) (bool, error) + ServiceActiveOnHost(ctx context.Context, service string) (bool, error) + GetHostSystemInfo(ctx context.Context) (*models.HostSystem, error) + GetIPAddresses(ctx context.Context) ([]string, error) + PathExists(path string) (bool, error) + IsLikelyDir(mountpoint string) (bool, error) + DeleteResourceAtPath(ctx context.Context, resource string) error + WaitForResourceDeletionAtPath(ctx context.Context, resource string, maxDuration time.Duration) error + EnsureFileExists(ctx context.Context, path string) error + EnsureDirExists(ctx context.Context, path string) error +} + +type OSUtils struct { + command exec.Command + osFs afero.Fs +} + +func New() *OSUtils { + return NewDetailed(exec.NewCommand(), afero.NewOsFs()) +} + +func NewDetailed(command exec.Command, osFs afero.Fs) *OSUtils { + return &OSUtils{ + command: command, + osFs: osFs, + } +} + func init() { if os.Getenv("DOCKER_PLUGIN_MODE") != "" { SetChrootPathPrefix("/host") @@ -38,11 +70,11 @@ func init() { func SetChrootPathPrefix(prefix string) { Logc(context.Background()).Debugf("SetChrootPathPrefix = '%s'", prefix) - chrootPathPrefix = prefix + ChrootPathPrefix = prefix } // GetIPAddresses returns the sorted list of Global Unicast IP addresses available to Trident -func GetIPAddresses(ctx context.Context) ([]string, error) { +func (o *OSUtils) GetIPAddresses(ctx context.Context) ([]string, error) { Logc(ctx).Debug(">>>> osutils.GetIPAddresses") defer Logc(ctx).Debug("<<<< osutils.GetIPAddresses") @@ -50,7 +82,7 @@ func GetIPAddresses(ctx context.Context) ([]string, error) { addrsMap := make(map[string]struct{}) // Get the set of potentially viable IP addresses for this host in an OS-appropriate way. - addrs, err := getIPAddresses(ctx) + addrs, err := o.getIPAddresses(ctx) if err != nil { err = fmt.Errorf("could not gather system IP addresses; %v", err) Logc(ctx).Error(err) @@ -75,17 +107,17 @@ func GetIPAddresses(ctx context.Context) ([]string, error) { return ipAddrs, nil } -func PathExists(path string) (bool, error) { - if _, err := os.Stat(path); err == nil { +func (o *OSUtils) PathExists(path string) (bool, error) { + if _, err := o.osFs.Stat(path); err == nil { return true, nil } return false, nil } // EnsureFileExists makes sure that file of given name exists -func EnsureFileExists(ctx context.Context, path string) error { +func (o *OSUtils) EnsureFileExists(ctx context.Context, path string) error { fields := LogFields{"path": path} - if info, err := os.Stat(path); err == nil { + if info, err := o.osFs.Stat(path); err == nil { if info.IsDir() { Logc(ctx).WithFields(fields).Error("Path exists but is a directory") return fmt.Errorf("path exists but is a directory: %s", path) @@ -96,7 +128,7 @@ func EnsureFileExists(ctx context.Context, path string) error { return fmt.Errorf("can't determine if file %s exists; %s", path, err) } - file, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC, 0o600) + file, err := o.osFs.OpenFile(path, os.O_CREATE|os.O_TRUNC, 0o600) if nil != err { Logc(ctx).WithFields(fields).Errorf("OpenFile failed; %s", err) return fmt.Errorf("failed to create file %s; %s", path, err) @@ -107,11 +139,11 @@ func EnsureFileExists(ctx context.Context, path string) error { } // DeleteResourceAtPath makes sure that given named file or (empty) directory is removed -func DeleteResourceAtPath(ctx context.Context, resource string) error { +func (o *OSUtils) DeleteResourceAtPath(ctx context.Context, resource string) error { fields := LogFields{"resource": resource} // Check if resource exists - if _, err := os.Stat(resource); err != nil { + if _, err := o.osFs.Stat(resource); err != nil { if os.IsNotExist(err) { Logc(ctx).WithFields(fields).Debugf("Resource not found.") return nil @@ -122,7 +154,7 @@ func DeleteResourceAtPath(ctx context.Context, resource string) error { } // Remove resource - if err := os.Remove(resource); err != nil { + if err := o.osFs.Remove(resource); err != nil { Logc(ctx).WithFields(fields).Debugf("Failed to remove resource, %s", err) return fmt.Errorf("failed to remove resource %s; %s", resource, err) } @@ -131,13 +163,13 @@ func DeleteResourceAtPath(ctx context.Context, resource string) error { } // WaitForResourceDeletionAtPath accepts a resource name and waits until it is deleted and returns error if it times out -func WaitForResourceDeletionAtPath(ctx context.Context, resource string, maxDuration time.Duration) error { +func (o *OSUtils) WaitForResourceDeletionAtPath(ctx context.Context, resource string, maxDuration time.Duration) error { fields := LogFields{"resource": resource} Logc(ctx).WithFields(fields).Debug(">>>> osutils.WaitForResourceDeletionAtPath") defer Logc(ctx).WithFields(fields).Debug("<<<< osutils.WaitForResourceDeletionAtPath") checkResourceDeletion := func() error { - return DeleteResourceAtPath(ctx, resource) + return o.DeleteResourceAtPath(ctx, resource) } deleteNotify := func(err error, duration time.Duration) { @@ -160,11 +192,11 @@ func WaitForResourceDeletionAtPath(ctx context.Context, resource string, maxDura } // EnsureDirExists makes sure that given directory structure exists -func EnsureDirExists(ctx context.Context, path string) error { +func (o *OSUtils) EnsureDirExists(ctx context.Context, path string) error { fields := LogFields{ "path": path, } - if info, err := os.Stat(path); err == nil { + if info, err := o.osFs.Stat(path); err == nil { if !info.IsDir() { Logc(ctx).WithFields(fields).Error("Path exists but is not a directory") return fmt.Errorf("path exists but is not a directory: %s", path) @@ -175,7 +207,7 @@ func EnsureDirExists(ctx context.Context, path string) error { return fmt.Errorf("can't determine if directory %s exists; %s", path, err) } - err := os.MkdirAll(path, 0o755) + err := o.osFs.MkdirAll(path, 0o755) if err != nil { Logc(ctx).WithFields(fields).Errorf("Mkdir failed; %s", err) return fmt.Errorf("failed to mkdir %s; %s", path, err) @@ -183,3 +215,8 @@ func EnsureDirExists(ctx context.Context, path string) error { return nil } + +// Detect if code is running in a container or not +func runningInContainer() bool { + return os.Getenv("CSI_ENDPOINT") != "" +} diff --git a/utils/osutils_darwin.go b/utils/osutils/osutils_darwin.go similarity index 72% rename from utils/osutils_darwin.go rename to utils/osutils/osutils_darwin.go index bba8e527c..7ab4eff5e 100644 --- a/utils/osutils_darwin.go +++ b/utils/osutils/osutils_darwin.go @@ -1,11 +1,10 @@ // Copyright 2022 NetApp, Inc. All Rights Reserved. -package utils +package osutils import ( "context" "net" - "os" . "github.com/netapp/trident/logging" "github.com/netapp/trident/utils/errors" @@ -17,29 +16,29 @@ import ( // of the Trident code base this file exists to handle darwin specific code. // getIPAddresses unused stub function -func getIPAddresses(ctx context.Context) ([]net.Addr, error) { +func (o *OSUtils) getIPAddresses(ctx context.Context) ([]net.Addr, error) { Logc(ctx).Debug(">>>> osutils_darwin.getIPAddresses") defer Logc(ctx).Debug("<<<< osutils_darwin.getIPAddresses") return nil, errors.UnsupportedError("getIPAddresses is not supported for darwin") } // GetHostSystemInfo unused stub function -func GetHostSystemInfo(ctx context.Context) (*models.HostSystem, error) { +func (o *OSUtils) GetHostSystemInfo(ctx context.Context) (*models.HostSystem, error) { Logc(ctx).Debug(">>>> osutils_darwin.GetHostSystemInfo") defer Logc(ctx).Debug("<<<< osutils_darwin.GetHostSystemInfo") return nil, errors.UnsupportedError("GetHostSystemInfo is not supported for darwin") } // NFSActiveOnHost unused stub function -func NFSActiveOnHost(ctx context.Context) (bool, error) { +func (o *OSUtils) NFSActiveOnHost(ctx context.Context) (bool, error) { Logc(ctx).Debug(">>>> osutils_darwin.NFSActiveOnHost") defer Logc(ctx).Debug("<<<< osutils_darwin.NFSActiveOnHost") return false, errors.UnsupportedError("NFSActiveOnHost is not supported for darwin") } // IsLikelyDir determines if mountpoint is a directory -func IsLikelyDir(mountpoint string) (bool, error) { - stat, err := os.Stat(mountpoint) +func (o *OSUtils) IsLikelyDir(mountpoint string) (bool, error) { + stat, err := o.osFs.Stat(mountpoint) if err != nil { return false, err } @@ -60,3 +59,10 @@ func SMBActiveOnHost(ctx context.Context) (bool, error) { defer Logc(ctx).Debug("<<<< osutils_darwin.SMBActiveOnHost") return false, errors.UnsupportedError("SMBActiveOnHost is not supported for darwin") } + +// ServiceActiveOnHost checks if the service is currently running +func (o *OSUtils) ServiceActiveOnHost(ctx context.Context, service string) (bool, error) { + Logc(ctx).Debug(">>>> osutils_darwin.ServiceActiveOnHost") + defer Logc(ctx).Debug("<<<< osutils_darwin.ServiceActiveOnHost") + return false, errors.UnsupportedError("ServiceActiveOnHost is not supported for darwin") +} diff --git a/utils/osutils_darwin_test.go b/utils/osutils/osutils_darwin_test.go similarity index 82% rename from utils/osutils_darwin_test.go rename to utils/osutils/osutils_darwin_test.go index e37a7c7a2..9a61586d8 100644 --- a/utils/osutils_darwin_test.go +++ b/utils/osutils/osutils_darwin_test.go @@ -1,4 +1,4 @@ -package utils +package osutils import ( "context" @@ -11,7 +11,8 @@ import ( func TestGetIPAddresses(t *testing.T) { ctx := context.Background() - result, err := getIPAddresses(ctx) + utils := New() + result, err := utils.getIPAddresses(ctx) assert.Nil(t, result, "got ip address") assert.Error(t, err, "no error") assert.True(t, errors.IsUnsupportedError(err), "not UnsupportedError") @@ -19,7 +20,8 @@ func TestGetIPAddresses(t *testing.T) { func TestGetHostSystemInfo(t *testing.T) { ctx := context.Background() - result, err := GetHostSystemInfo(ctx) + utils := New() + result, err := utils.GetHostSystemInfo(ctx) assert.Nil(t, result, "got host system info") assert.Error(t, err, "no error") assert.True(t, errors.IsUnsupportedError(err), "not UnsupportedError") @@ -27,20 +29,23 @@ func TestGetHostSystemInfo(t *testing.T) { func TestNFSActiveOnHost(t *testing.T) { ctx := context.Background() - result, err := NFSActiveOnHost(ctx) + utils := New() + result, err := utils.NFSActiveOnHost(ctx) assert.False(t, result, "nfs is present on the host") assert.Error(t, err, "no error") assert.True(t, errors.IsUnsupportedError(err), "not UnsupportedError") } func TestIsLikelyDir_NotPresent(t *testing.T) { - result, err := IsLikelyDir("\\usr") + utils := New() + result, err := utils.IsLikelyDir("\\usr") assert.False(t, result, "directory exists") assert.Error(t, err, "no error") } func TestIsLikelyDir_Present(t *testing.T) { - result, err := IsLikelyDir("/Users") + utils := New() + result, err := utils.IsLikelyDir("/Users") assert.True(t, result, "directory doesn't exists") assert.NoError(t, err, "error occurred") } diff --git a/utils/osutils_linux.go b/utils/osutils/osutils_linux.go similarity index 80% rename from utils/osutils_linux.go rename to utils/osutils/osutils_linux.go index 4e2a08232..4ac0877fa 100644 --- a/utils/osutils_linux.go +++ b/utils/osutils/osutils_linux.go @@ -1,13 +1,12 @@ // Copyright 2022 NetApp, Inc. All Rights Reserved. -package utils +package osutils import ( "context" "encoding/json" "fmt" "net" - "os" "os/exec" "path" "time" @@ -21,19 +20,19 @@ import ( ) // NFSActiveOnHost will return if the rpc-statd daemon is active on the given host -func NFSActiveOnHost(ctx context.Context) (bool, error) { +func (o *OSUtils) NFSActiveOnHost(ctx context.Context) (bool, error) { Logc(ctx).Debug(">>>> osutils_linux.NFSActiveOnHost") defer Logc(ctx).Debug("<<<< osutils_linux.NFSActiveOnHost") - return ServiceActiveOnHost(ctx, "rpc-statd") + return o.ServiceActiveOnHost(ctx, "rpc-statd") } // ServiceActiveOnHost checks if the service is currently running -func ServiceActiveOnHost(ctx context.Context, service string) (bool, error) { +func (o *OSUtils) ServiceActiveOnHost(ctx context.Context, service string) (bool, error) { Logc(ctx).Debug(">>>> osutils_linux.ServiceActiveOnHost") defer Logc(ctx).Debug("<<<< osutils_linux.ServiceActiveOnHost") - output, err := command.ExecuteWithTimeout(ctx, "systemctl", 30*time.Second, true, "is-active", service) + output, err := o.command.ExecuteWithTimeout(ctx, "systemctl", 30*time.Second, true, "is-active", service) if err != nil { if _, ok := err.(*exec.ExitError); ok { Logc(ctx).WithField("service", service).Debug("Service is not active on the host.") @@ -49,7 +48,7 @@ func ServiceActiveOnHost(ctx context.Context, service string) (bool, error) { } // GetHostSystemInfo returns information about the host system -func GetHostSystemInfo(ctx context.Context) (*models.HostSystem, error) { +func (o *OSUtils) GetHostSystemInfo(ctx context.Context) (*models.HostSystem, error) { Logc(ctx).Debug(">>>> osutils_linux.GetHostSystemInfo") defer Logc(ctx).Debug("<<<< osutils_linux.GetHostSystemInfo") @@ -61,11 +60,12 @@ func GetHostSystemInfo(ctx context.Context) (*models.HostSystem, error) { osInfo := sysinfo.OS{} msg := "Problem reading host system info." - if RunningInContainer() { + if runningInContainer() { // Get the hosts' info via tridentctl because the sysInfo library needs to be chrooted in order to detect // the host OS and not the container's but chroot is irreversible and thus needs to run in a separate // short-lived binary - data, err = command.ExecuteWithTimeout(ctx, "tridentctl", 5*time.Second, true, "system", "--chroot-path", "/host") + data, err = o.command.ExecuteWithTimeout(ctx, "tridentctl", 5*time.Second, true, "system", "--chroot-path", + "/host") if err != nil { Logc(ctx).WithField("err", err).Error(msg) return nil, err @@ -92,7 +92,7 @@ func GetHostSystemInfo(ctx context.Context) (*models.HostSystem, error) { } // getIPAddresses uses the Linux-specific netlink library to get a host's external IP addresses. -func getIPAddresses(ctx context.Context) ([]net.Addr, error) { +func (o *OSUtils) getIPAddresses(ctx context.Context) ([]net.Addr, error) { Logc(ctx).Debug(">>>> osutils_linux.getIPAddresses") defer Logc(ctx).Debug("<<<< osutils_linux.getIPAddresses") @@ -100,7 +100,7 @@ func getIPAddresses(ctx context.Context) ([]net.Addr, error) { addressMap := make(map[string]net.Addr) // Consider addresses from non-dummy interfaces - addresses, err := getIPAddressesExceptingDummyInterfaces(ctx) + addresses, err := o.getIPAddressesExceptingDummyInterfaces(ctx) if err != nil { return nil, err } @@ -109,7 +109,7 @@ func getIPAddresses(ctx context.Context) ([]net.Addr, error) { } // Consider addresses from interfaces on default routes - addresses, err = getIPAddressesExceptingNondefaultRoutes(ctx) + addresses, err = o.getIPAddressesExceptingNondefaultRoutes(ctx) if err != nil { return nil, err } @@ -125,11 +125,11 @@ func getIPAddresses(ctx context.Context) ([]net.Addr, error) { } // getIPAddressesExceptingDummyInterfaces returns all global unicast addresses from non-dummy interfaces. -func getIPAddressesExceptingDummyInterfaces(ctx context.Context) ([]net.Addr, error) { +func (o *OSUtils) getIPAddressesExceptingDummyInterfaces(ctx context.Context) ([]net.Addr, error) { Logc(ctx).Debug(">>>> osutils_linux.getAddressesExceptingDummyInterfaces") defer Logc(ctx).Debug("<<<< osutils_linux.getAddressesExceptingDummyInterfaces") - allLinks, err := netlink.LinkList() + allLinks, err := netLink.LinkList() if err != nil { Logc(ctx).Error(err) return nil, err @@ -149,16 +149,16 @@ func getIPAddressesExceptingDummyInterfaces(ctx context.Context) ([]net.Addr, er links = append(links, link) } - return getUsableAddressesFromLinks(ctx, links), nil + return o.getUsableAddressesFromLinks(ctx, links), nil } // getIPAddressesExceptingNondefaultRoutes returns all global unicast addresses from interfaces on default routes. -func getIPAddressesExceptingNondefaultRoutes(ctx context.Context) ([]net.Addr, error) { +func (o *OSUtils) getIPAddressesExceptingNondefaultRoutes(ctx context.Context) ([]net.Addr, error) { Logc(ctx).Debug(">>>> osutils_linux.getAddressesExceptingNondefaultRoutes") defer Logc(ctx).Debug("<<<< osutils_linux.getAddressesExceptingNondefaultRoutes") // Get all default routes (nil destination) - routes, err := netlink.RouteListFiltered(netlink.FAMILY_ALL, &netlink.Route{}, netlink.RT_FILTER_DST) + routes, err := netLink.RouteListFiltered(netlink.FAMILY_ALL, &netlink.Route{}, netlink.RT_FILTER_DST) if err != nil { Logc(ctx).Error(err) return nil, err @@ -173,18 +173,18 @@ func getIPAddressesExceptingNondefaultRoutes(ctx context.Context) ([]net.Addr, e links := make([]netlink.Link, 0) for linkIndex := range intfIndexMap { - if link, err := netlink.LinkByIndex(linkIndex); err != nil { + if link, err := netLink.LinkByIndex(linkIndex); err != nil { Logc(ctx).Error(err) } else { links = append(links, link) } } - return getUsableAddressesFromLinks(ctx, links), nil + return o.getUsableAddressesFromLinks(ctx, links), nil } // getUsableAddressesFromLinks returns all global unicast addresses on the specified interfaces. -func getUsableAddressesFromLinks(ctx context.Context, links []netlink.Link) []net.Addr { +func (o *OSUtils) getUsableAddressesFromLinks(ctx context.Context, links []netlink.Link) []net.Addr { addrs := make([]net.Addr, 0) for _, link := range links { @@ -192,7 +192,7 @@ func getUsableAddressesFromLinks(ctx context.Context, links []netlink.Link) []ne logFields := LogFields{"interface": link.Attrs().Name, "type": link.Type()} Logc(ctx).WithFields(logFields).Debug("Considering interface.") - linkAddrs, err := netlink.AddrList(link, netlink.FAMILY_ALL) + linkAddrs, err := netLink.AddrList(link, netlink.FAMILY_ALL) if err != nil { Log().WithFields(logFields).Errorf("Could not get addresses for interface; %v", err) continue @@ -222,8 +222,8 @@ func getUsableAddressesFromLinks(ctx context.Context, links []netlink.Link) []ne } // IsLikelyDir determines if mountpoint is a directory -func IsLikelyDir(mountpoint string) (bool, error) { - stat, err := os.Stat(mountpoint) +func (o *OSUtils) IsLikelyDir(mountpoint string) (bool, error) { + stat, err := o.osFs.Stat(mountpoint) if err != nil { return false, err } diff --git a/utils/osutils/osutils_linux_test.go b/utils/osutils/osutils_linux_test.go new file mode 100644 index 000000000..ac0ea15ed --- /dev/null +++ b/utils/osutils/osutils_linux_test.go @@ -0,0 +1,248 @@ +// Copyright 2022 NetApp, Inc. All Rights Reserved. + +//go:build linux + +package osutils + +import ( + "context" + "fmt" + "net" + "os" + "strings" + "testing" + "time" + + "github.com/spf13/afero" + "github.com/stretchr/testify/assert" + "github.com/vishvananda/netlink" + "go.uber.org/mock/gomock" + + mockexec "github.com/netapp/trident/mocks/mock_utils/mock_exec" + "github.com/netapp/trident/mocks/mock_utils/mock_osutils" + "github.com/netapp/trident/utils/errors" + "github.com/netapp/trident/utils/exec" +) + +func TestGetIPAddresses(t *testing.T) { + osUtils := New() + addrs, err := osUtils.GetIPAddresses(context.TODO()) + if err != nil { + t.Error(err) + } + + assert.Greater(t, len(addrs), 0, "No IP addresses found") + + for _, ip := range addrs { + assert.NotNil(t, net.ParseIP(ip), "IP address is not valid") + } +} + +func TestGetIPAddresses_Error(t *testing.T) { + mockNetLink := mock_osutils.NewMockNetLink(gomock.NewController(t)) + mockNetLink.EXPECT().LinkList().Return(nil, fmt.Errorf("error")) + + defer func(originalNetlink NetLink) { + netLink = originalNetlink + }(netLink) + netLink = mockNetLink + + osUtils := NewDetailed(nil, afero.NewMemMapFs()) + addrs, err := osUtils.GetIPAddresses(context.TODO()) + assert.Nil(t, addrs) + assert.Error(t, err) +} + +func TestGetIPAddressesExceptingDummyInterfaces(t *testing.T) { + osUtils := New() + addrs, err := osUtils.getIPAddressesExceptingDummyInterfaces(context.TODO()) + if err != nil { + t.Error(err) + } + + assert.Greater(t, len(addrs), 0, "No IP addresses found") + + for _, addr := range addrs { + + parsedAddr := net.ParseIP(strings.Split(addr.String(), "/")[0]) + assert.False(t, parsedAddr.IsLoopback(), "Address is loopback") + assert.True(t, parsedAddr.IsGlobalUnicast(), "Address is not global unicast") + } +} + +func TestGetIPAddressesExceptingDummyInterfaces_NegTests(t *testing.T) { + mockNetLink := mock_osutils.NewMockNetLink(gomock.NewController(t)) + mockNetLink.EXPECT().LinkList().Return(nil, fmt.Errorf("error")) + + defer func(originalNetlink NetLink) { + netLink = originalNetlink + }(netLink) + netLink = mockNetLink + + osUtils := NewDetailed(nil, afero.NewMemMapFs()) + addrs, err := osUtils.getIPAddressesExceptingDummyInterfaces(context.TODO()) + assert.Nil(t, addrs) + assert.Error(t, err) +} + +func TestGetIPAddressesExceptingDummyInterfaces_DummyType(t *testing.T) { + mockLinks := []netlink.Link{ + &netlink.Dummy{}, + } + mockNetLink := mock_osutils.NewMockNetLink(gomock.NewController(t)) + mockNetLink.EXPECT().LinkList().Return(mockLinks, nil) + + defer func(originalNetlink NetLink) { + netLink = originalNetlink + }(netLink) + netLink = mockNetLink + + osUtils := NewDetailed(nil, afero.NewMemMapFs()) + addrs, err := osUtils.getIPAddressesExceptingDummyInterfaces(context.TODO()) + assert.Equal(t, 0, len(addrs)) + assert.NoError(t, err) +} + +func TestGetIPAddressesExceptingNondefaultRoutes(t *testing.T) { + osUtils := New() + addrs, err := osUtils.getIPAddressesExceptingNondefaultRoutes(context.TODO()) + if err != nil { + t.Error(err) + } + + assert.Greater(t, len(addrs), 0, "No IP addresses found") + + for _, addr := range addrs { + + parsedAddr := net.ParseIP(strings.Split(addr.String(), "/")[0]) + assert.False(t, parsedAddr.IsLoopback(), "Address is loopback") + assert.True(t, parsedAddr.IsGlobalUnicast(), "Address is not global unicast") + } +} + +func TestNFSActiveOnHost(t *testing.T) { + mockCtrl := gomock.NewController(t) + mockExec := mockexec.NewMockCommand(mockCtrl) + + tests := []struct { + name string + expectedErr error + }{ + { + name: "Active", + expectedErr: nil, + }, + { + name: "Inactive", + expectedErr: nil, + }, + { + name: "Fails", + expectedErr: fmt.Errorf("failed to check if service is active on host"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + os := NewDetailed(mockExec, afero.NewMemMapFs()) + + ctx := context.Background() + mockExec.EXPECT().ExecuteWithTimeout( + ctx, "systemctl", 30*time.Second, true, "is-active", "rpc-statd", + ).Return([]byte(""), tt.expectedErr) + + active, err := os.NFSActiveOnHost(ctx) + if tt.expectedErr != nil { + assert.Error(t, err) + assert.False(t, active) + } else { + assert.NoError(t, err) + assert.True(t, active) + } + }) + } +} + +func TestGetTargetFilePath(t *testing.T) { + ctx := context.Background() + result := GetTargetFilePath(ctx, "/host/path1", "/test/path") + assert.NotEmpty(t, result, "path is empty") +} + +func TestSMBActiveOnHost(t *testing.T) { + ctx := context.Background() + result, err := SMBActiveOnHost(ctx) + assert.False(t, result, "smb is active on the host") + assert.Error(t, err, "no error") + assert.True(t, errors.IsUnsupportedError(err), "not UnsupportedError") +} + +func TestGetHostSystemInfo(t *testing.T) { + value, isSet := os.LookupEnv("CSI_ENDPOINT") + if isSet { + defer os.Setenv("CSI_ENDPOINT", value) + } + + ctx := context.Background() + + // Not in container + os.Unsetenv("CSI_ENDPOINT") + osUtils := NewDetailed(exec.NewCommand(), afero.NewMemMapFs()) + hostInfo, err := osUtils.GetHostSystemInfo(ctx) + assert.NotNil(t, hostInfo) + assert.NoError(t, err, "error is not nil") + assert.NotEqual(t, hostInfo.OS.Distro, "") + + // In container + t.Setenv("CSI_ENDPOINT", "unix:///csi/csi.sock") + mockCmd := mockexec.NewMockCommand(gomock.NewController(t)) + mockData := `{ + "name": "Linux", + "vendor": "Ubuntu", + "version": "5.4.0-42-generic", + "arch": "x86_64" + }` + mockCmd.EXPECT().ExecuteWithTimeout(ctx, "tridentctl", 5*time.Second, true, "system", "--chroot-path", + "/host").Return([]byte(mockData), nil) + osUtils = NewDetailed(mockCmd, afero.NewMemMapFs()) + hostInfo, err = osUtils.GetHostSystemInfo(ctx) + assert.NotNil(t, hostInfo) + assert.NoError(t, err, "error is not nil") + assert.NotEqual(t, hostInfo.OS.Distro, "") + + // ExecuteWithTimeout error + mockCmd.EXPECT().ExecuteWithTimeout(ctx, "tridentctl", 5*time.Second, true, "system", "--chroot-path", + "/host").Return(nil, fmt.Errorf("error")) + osUtils = NewDetailed(mockCmd, afero.NewMemMapFs()) + hostInfo, err = osUtils.GetHostSystemInfo(ctx) + assert.Error(t, err, "error is not nil") +} + +func TestGetUsableAddressesFromLinks(t *testing.T) { + mockLinks := []netlink.Link{ + &netlink.Veth{}, + &netlink.Veth{}, + } + + mockAddresses := []netlink.Addr{ + // Nil case + {IPNet: nil}, + // Global Unicast case + {IPNet: &net.IPNet{IP: net.IPv4(255, 255, 255, 255), Mask: net.CIDRMask(24, 32)}}, + // Valid IP + {IPNet: &net.IPNet{IP: net.IPv4(10, 10, 10, 10), Mask: net.CIDRMask(24, 32)}}, + } + + mockNetLink := mock_osutils.NewMockNetLink(gomock.NewController(t)) + mockNetLink.EXPECT().AddrList(gomock.Any(), netlink.FAMILY_ALL).Return(nil, fmt.Errorf("error")) + mockNetLink.EXPECT().AddrList(gomock.Any(), netlink.FAMILY_ALL).Return(mockAddresses, nil) + defer func(originalNetlink NetLink) { + netLink = originalNetlink + }(netLink) + netLink = mockNetLink + + osUtils := NewDetailed(exec.NewCommand(), afero.NewMemMapFs()) + addresses := osUtils.getUsableAddressesFromLinks(context.TODO(), mockLinks) + assert.Equal(t, 1, len(addresses)) + assert.Equal(t, "10.10.10.10/24", addresses[0].String()) +} diff --git a/utils/osutils/osutils_test.go b/utils/osutils/osutils_test.go new file mode 100644 index 000000000..660226e89 --- /dev/null +++ b/utils/osutils/osutils_test.go @@ -0,0 +1,220 @@ +// Copyright 2024 NetApp, Inc. All Rights Reserved. + +package osutils + +import ( + "context" + b64 "encoding/base64" + "fmt" + "os" + "strconv" + "testing" + "time" + + "github.com/spf13/afero" + "github.com/stretchr/testify/assert" + + execCmd "github.com/netapp/trident/utils/exec" +) + +// TestShellProcess is a method that is called as a substitute for a shell command, +// the GO_TEST flag ensures that if it is called as part of the test suite, it is +// skipped. GO_TEST_RETURN_VALUE flag allows the caller to specify a base64 encoded version of what should be returned via stdout, +// GO_TEST_RETURN_CODE flag allows the caller to specify what the return code should be, and +// GO_TEST_DELAY flag allows the caller to inject a delay before the function returns. +// GO_TEST_RETURN_PADDING_LENGTH flag allows the caller to specify how many bytes to return in total, +// if GO_TEST_RETURN_VALUE does not use all the bytes, the end is padded with NUL data +func TestShellProcess(t *testing.T) { + if os.Getenv("GO_TEST") != "1" { + return + } + // Print out the test value to stdout + returnString, _ := b64.StdEncoding.DecodeString(os.Getenv("GO_TEST_RETURN_VALUE")) + fmt.Fprintf(os.Stdout, string(returnString)) + if os.Getenv("GO_TEST_RETURN_PADDING_LENGTH") != "" { + padLength, _ := strconv.Atoi(os.Getenv("GO_TEST_RETURN_PADDING_LENGTH")) + padString := make([]byte, padLength-len(returnString)) + fmt.Fprintf(os.Stdout, string(padString)) + } + code, err := strconv.Atoi(os.Getenv("GO_TEST_RETURN_CODE")) + if err != nil { + code = -1 + } + // Pause for some amount of time + delay, err := time.ParseDuration(os.Getenv("GO_TEST_DELAY")) + if err == nil { + time.Sleep(delay) + } + os.Exit(code) +} + +func TestIsLikelyDir(t *testing.T) { + fs := afero.NewMemMapFs() + osUtils := NewDetailed(execCmd.NewCommand(), fs) + + testFilePath := "./deleteFileTest" + testDirPath := "./deleteDirTest" + fs.Create(testFilePath) + fs.Mkdir(testDirPath, 0o755) + + // Test that a directory is correctly identified as a directory + isDir, err := osUtils.IsLikelyDir("./") + assert.True(t, isDir) + assert.NoError(t, err) + + // Test that a file is correctly identified as not a directory + isDir, err = osUtils.IsLikelyDir(testFilePath) + assert.False(t, isDir) + assert.NoError(t, err) + + // Test that a non-existent path is correctly identified as not a directory + isDir, err = osUtils.IsLikelyDir("./nonexistent") + assert.False(t, isDir) + assert.Error(t, err) +} + +func TestPathExists(t *testing.T) { + fs := afero.NewMemMapFs() + osUtils := NewDetailed(execCmd.NewCommand(), fs) + + testFilePath := "./deleteFileTest" + testDirPath := "./deleteDirTest" + fs.Create(testFilePath) + fs.Mkdir(testDirPath, 0o755) + + // Test that a directory is correctly identified as existing + exists, err := osUtils.PathExists(testDirPath) + assert.True(t, exists) + assert.NoError(t, err) + + // Test that a file is correctly identified as existing + exists, err = osUtils.PathExists(testFilePath) + assert.True(t, exists) + assert.NoError(t, err) + + // Test that a non-existent path is correctly identified as not existing + exists, err = osUtils.PathExists("./nonexistent") + assert.False(t, exists) + assert.NoError(t, err) +} + +func TestEnsureFileExists(t *testing.T) { + fs := afero.NewMemMapFs() + osUtils := NewDetailed(execCmd.NewCommand(), fs) + + // File exists + testFileName := "testfile" + fs.Create(testFileName) + err := osUtils.EnsureFileExists(context.TODO(), testFileName) + assert.NoError(t, err) + + // Is directory + dirName := "testdir" + fs.Mkdir(dirName, 0o755) + err = osUtils.EnsureFileExists(context.TODO(), dirName) + assert.Error(t, err) + + // File does not exist + err = osUtils.EnsureFileExists(context.TODO(), "./nonexistent") + assert.NoError(t, err) + // Ensure file was created + _, err = fs.Stat("./nonexistent") + assert.NoError(t, err) +} + +func TestDeleteResourceAtPath(t *testing.T) { + fs := afero.NewMemMapFs() + osUtils := NewDetailed(execCmd.NewCommand(), fs) + + testFilePath := "./deleteFileTest" + testDirPath := "./deleteDirTest" + fs.Create(testFilePath) + fs.Mkdir(testDirPath, 0o755) + + // File exists + err := osUtils.DeleteResourceAtPath(context.TODO(), testFilePath) + assert.NoError(t, err) + _, err = os.Stat(testFilePath) + assert.Error(t, err, "file should not exist") + + // Is directory + err = osUtils.DeleteResourceAtPath(context.TODO(), testDirPath) + assert.NoError(t, err) + _, err = fs.Stat(testDirPath) + assert.Error(t, err, "directory should not exist") + + // File does not exist + err = osUtils.DeleteResourceAtPath(context.TODO(), testFilePath) + assert.NoError(t, err) + + // Dir does not exist + err = osUtils.DeleteResourceAtPath(context.TODO(), testDirPath) + assert.NoError(t, err) +} + +func TestWaitForResourceDeletionAtPath(t *testing.T) { + fs := afero.NewMemMapFs() + osUtils := NewDetailed(execCmd.NewCommand(), fs) + + testFilePath := "./deleteFileTest" + testDirPath := "./deleteDirTest" + fs.Create(testFilePath) + fs.Mkdir(testDirPath, 0o755) + + // File exists + err := osUtils.WaitForResourceDeletionAtPath(context.TODO(), testFilePath, 1*time.Second) + assert.NoError(t, err) + _, err = os.Stat(testFilePath) + assert.Error(t, err, "file should not exist") + + // Is directory + err = osUtils.WaitForResourceDeletionAtPath(context.TODO(), testDirPath, 1*time.Second) + assert.NoError(t, err) + _, err = fs.Stat(testDirPath) + assert.Error(t, err, "directory should not exist") + + // File does not exist + err = osUtils.WaitForResourceDeletionAtPath(context.TODO(), testFilePath, 1*time.Second) + assert.NoError(t, err) + + // Dir does not exist + err = osUtils.WaitForResourceDeletionAtPath(context.TODO(), testDirPath, 1*time.Second) + assert.NoError(t, err) +} + +func TestEnsureDirExists(t *testing.T) { + fs := afero.NewMemMapFs() + osUtils := NewDetailed(execCmd.NewCommand(), fs) + + testFilePath := "./deleteFileTest" + testDirPath := "./deleteDirTest" + fs.Create(testFilePath) + fs.Mkdir(testDirPath, 0o755) + + // Directory exists + err := osUtils.EnsureDirExists(context.TODO(), testDirPath) + assert.NoError(t, err) + + // Is file + err = osUtils.EnsureDirExists(context.TODO(), testFilePath) + assert.Error(t, err) + + // Directory does not exist + notExistsDir := "doesNotExist" + err = osUtils.EnsureDirExists(context.TODO(), notExistsDir) + assert.NoError(t, err) + _, err = fs.Stat(notExistsDir) + assert.NoError(t, err, "directory should exist") +} + +func TestRunningInContainer(t *testing.T) { + // Get env var + value := os.Getenv("CSI_ENDPOINT") + // Test that the function returns false when not running in a container + inContainer := runningInContainer() + if value == "" { + assert.False(t, inContainer) + } else { + assert.True(t, inContainer) + } +} diff --git a/utils/osutils_windows.go b/utils/osutils/osutils_windows.go similarity index 74% rename from utils/osutils_windows.go rename to utils/osutils/osutils_windows.go index 5ac7cdf50..98f5b8719 100644 --- a/utils/osutils_windows.go +++ b/utils/osutils/osutils_windows.go @@ -1,6 +1,6 @@ // Copyright 2022 NetApp, Inc. All Rights Reserved. -package utils +package osutils import ( "context" @@ -20,7 +20,7 @@ import ( // of the Trident code base this file exists to handle windows specific code. // getIPAddresses unused stub function -func getIPAddresses(ctx context.Context) ([]net.Addr, error) { +func (o *OSUtils) getIPAddresses(ctx context.Context) ([]net.Addr, error) { Logc(ctx).Debug(">>>> osutils_windows.getIPAddresses") defer Logc(ctx).Debug("<<<< osutils_windows.getIPAddresses") return nil, errors.UnsupportedError("getIPAddresses is not supported for windows") @@ -34,7 +34,7 @@ func SMBActiveOnHost(ctx context.Context) (bool, error) { } // GetHostSystemInfo returns the information about OS type and platform -func GetHostSystemInfo(ctx context.Context) (*models.HostSystem, error) { +func (o *OSUtils) GetHostSystemInfo(ctx context.Context) (*models.HostSystem, error) { osInfo, err := sysinfo.Host() if err != nil { return nil, err @@ -51,7 +51,7 @@ func GetHostSystemInfo(ctx context.Context) (*models.HostSystem, error) { } // NFSActiveOnHost unused stub function -func NFSActiveOnHost(ctx context.Context) (bool, error) { +func (o *OSUtils) NFSActiveOnHost(ctx context.Context) (bool, error) { Logc(ctx).Debug(">>>> osutils_windows.NFSActiveOnHost") defer Logc(ctx).Debug("<<<< osutils_windows.NFSActiveOnHost") return false, errors.UnsupportedError("NFSActiveOnHost is not supported for windows") @@ -66,6 +66,13 @@ func GetTargetFilePath(ctx context.Context, resourcePath, arg string) string { } // IsLikelyDir determines if mountpoint is a directory -func IsLikelyDir(mountpoint string) (bool, error) { - return PathExists(mountpoint) +func (o *OSUtils) IsLikelyDir(mountpoint string) (bool, error) { + return o.PathExists(mountpoint) +} + +// ServiceActiveOnHost checks if the service is currently running +func (o *OSUtils) ServiceActiveOnHost(ctx context.Context, service string) (bool, error) { + Logc(ctx).Debug(">>>> osutils_windows.ServiceActiveOnHost") + defer Logc(ctx).Debug("<<<< osutils_windows.ServiceActiveOnHost") + return false, errors.UnsupportedError("ServiceActiveOnHost is not supported for windows") } diff --git a/utils/osutils_windows_test.go b/utils/osutils/osutils_windows_test.go similarity index 85% rename from utils/osutils_windows_test.go rename to utils/osutils/osutils_windows_test.go index ec641a9f3..848d028ff 100644 --- a/utils/osutils_windows_test.go +++ b/utils/osutils/osutils_windows_test.go @@ -1,6 +1,6 @@ // Copyright 2022 NetApp, Inc. All Rights Reserved. -package utils +package osutils import ( "context" @@ -13,14 +13,16 @@ import ( func TestGetHostSystemInfo(t *testing.T) { ctx := context.Background() - result, err := GetHostSystemInfo(ctx) + osutils := New() + result, err := osutils.GetHostSystemInfo(ctx) assert.NotNil(t, result, "host system information is not populated") assert.NoError(t, err, "no error") } func TestNFSActiveOnHost(t *testing.T) { ctx := context.Background() - result, err := NFSActiveOnHost(ctx) + osutils := New() + result, err := osutils.NFSActiveOnHost(ctx) assert.False(t, result, "nfs is present on the host") assert.Error(t, err, "no error") assert.True(t, errors.IsUnsupportedError(err), "not UnsupportedError") @@ -34,7 +36,8 @@ func TestGetTargetFilePath(t *testing.T) { func TestGetIPAddresses(t *testing.T) { ctx := context.Background() - result, err := getIPAddresses(ctx) + osutils := New() + result, err := osutils.getIPAddresses(ctx) assert.Nil(t, result, "got ip address") assert.Error(t, err, "no error") assert.True(t, errors.IsUnsupportedError(err), "not UnsupportedError") diff --git a/utils/osutils_linux_test.go b/utils/osutils_linux_test.go deleted file mode 100644 index ac5f958eb..000000000 --- a/utils/osutils_linux_test.go +++ /dev/null @@ -1,235 +0,0 @@ -// Copyright 2022 NetApp, Inc. All Rights Reserved. - -//go:build linux - -package utils - -import ( - "context" - "fmt" - "net" - "strings" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "go.uber.org/mock/gomock" - - mockexec "github.com/netapp/trident/mocks/mock_utils/mock_exec" - "github.com/netapp/trident/utils/errors" - "github.com/netapp/trident/utils/exec" - "github.com/netapp/trident/utils/models" -) - -func TestGetIPAddresses(t *testing.T) { - addrs, err := getIPAddresses(context.TODO()) - if err != nil { - t.Error(err) - } - - assert.Greater(t, len(addrs), 0, "No IP addresses found") - - for _, addr := range addrs { - - parsedAddr := net.ParseIP(strings.Split(addr.String(), "/")[0]) - assert.False(t, parsedAddr.IsLoopback(), "Address is loopback") - assert.True(t, parsedAddr.IsGlobalUnicast(), "Address is not global unicast") - } -} - -func TestGetIPAddressesExceptingDummyInterfaces(t *testing.T) { - addrs, err := getIPAddressesExceptingDummyInterfaces(context.TODO()) - if err != nil { - t.Error(err) - } - - assert.Greater(t, len(addrs), 0, "No IP addresses found") - - for _, addr := range addrs { - - parsedAddr := net.ParseIP(strings.Split(addr.String(), "/")[0]) - assert.False(t, parsedAddr.IsLoopback(), "Address is loopback") - assert.True(t, parsedAddr.IsGlobalUnicast(), "Address is not global unicast") - } -} - -func TestGetIPAddressesExceptingNondefaultRoutes(t *testing.T) { - addrs, err := getIPAddressesExceptingNondefaultRoutes(context.TODO()) - if err != nil { - t.Error(err) - } - - assert.Greater(t, len(addrs), 0, "No IP addresses found") - - for _, addr := range addrs { - - parsedAddr := net.ParseIP(strings.Split(addr.String(), "/")[0]) - assert.False(t, parsedAddr.IsLoopback(), "Address is loopback") - assert.True(t, parsedAddr.IsGlobalUnicast(), "Address is not global unicast") - } -} - -func TestNFSActiveOnHost(t *testing.T) { - mockCtrl := gomock.NewController(t) - mockExec := mockexec.NewMockCommand(mockCtrl) - - tests := []struct { - name string - expectedErr error - }{ - { - name: "Active", - expectedErr: nil, - }, - { - name: "Inactive", - expectedErr: nil, - }, - { - name: "Fails", - expectedErr: fmt.Errorf("failed to check if service is active on host"), - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Reset exec command after each test. - defer func(previousCommand exec.Command) { - command = previousCommand - }(command) - - ctx := context.Background() - mockExec.EXPECT().ExecuteWithTimeout( - ctx, "systemctl", 30*time.Second, true, "is-active", "rpc-statd", - ).Return([]byte(""), tt.expectedErr) - command = mockExec - - active, err := NFSActiveOnHost(ctx) - if tt.expectedErr != nil { - assert.Error(t, err) - assert.False(t, active) - } else { - assert.NoError(t, err) - assert.True(t, active) - } - }) - } -} - -func TestISCSIActiveOnHost(t *testing.T) { - mockCtrl := gomock.NewController(t) - mockExec := mockexec.NewMockCommand(mockCtrl) - - type args struct { - ctx context.Context - host models.HostSystem - } - type expected struct { - active bool - err error - } - tests := []struct { - name string - args args - expected expected - service string - }{ - { - name: "ActiveCentos", - args: args{ - ctx: context.Background(), - host: models.HostSystem{OS: models.SystemOS{Distro: Centos}}, - }, - expected: expected{ - active: true, - err: nil, - }, - service: "iscsid", - }, - { - name: "ActiveRHEL", - args: args{ - ctx: context.Background(), - host: models.HostSystem{OS: models.SystemOS{Distro: RHEL}}, - }, - expected: expected{ - active: true, - err: nil, - }, - service: "iscsid", - }, - { - name: "ActiveUbuntu", - args: args{ - ctx: context.Background(), - host: models.HostSystem{OS: models.SystemOS{Distro: Ubuntu}}, - }, - expected: expected{ - active: true, - err: nil, - }, - service: "open-iscsi", - }, - { - name: "InactiveRHEL", - args: args{ - ctx: context.Background(), - host: models.HostSystem{OS: models.SystemOS{Distro: RHEL}}, - }, - expected: expected{ - active: false, - err: nil, - }, - service: "iscsid", - }, - { - name: "UnknownDistro", - args: args{ - ctx: context.Background(), - host: models.HostSystem{OS: models.SystemOS{Distro: "SUSE"}}, - }, - expected: expected{ - active: true, - err: nil, - }, - service: "iscsid", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Reset exec command after each test. - defer func(previousCommand exec.Command) { - command = previousCommand - }(command) - - mockExec.EXPECT().ExecuteWithTimeout( - tt.args.ctx, "systemctl", 30*time.Second, true, "is-active", tt.service, - ).Return([]byte(""), tt.expected.err) - command = mockExec - - active, err := ISCSIActiveOnHost(tt.args.ctx, tt.args.host) - if tt.expected.err != nil { - assert.Error(t, err) - assert.False(t, active) - } else { - assert.NoError(t, err) - assert.True(t, active) - } - }) - } -} - -func TestGetTargetFilePath(t *testing.T) { - ctx := context.Background() - result := GetTargetFilePath(ctx, "/host/path1", "/test/path") - assert.NotEmpty(t, result, "path is empty") -} - -func TestSMBActiveOnHost(t *testing.T) { - ctx := context.Background() - result, err := SMBActiveOnHost(ctx) - assert.False(t, result, "smb is active on the host") - assert.Error(t, err, "no error") - assert.True(t, errors.IsUnsupportedError(err), "not UnsupportedError") -} diff --git a/utils/osutils_test.go b/utils/osutils_test.go deleted file mode 100644 index 0c2ea6b2e..000000000 --- a/utils/osutils_test.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2024 NetApp, Inc. All Rights Reserved. - -package utils - -import ( - b64 "encoding/base64" - "fmt" - "os" - "strconv" - "testing" - "time" -) - -var ( - execReturnValue string - execReturnCode int - execPadding int - execDelay time.Duration -) - -// TestShellProcess is a method that is called as a substitute for a shell command, -// the GO_TEST flag ensures that if it is called as part of the test suite, it is -// skipped. GO_TEST_RETURN_VALUE flag allows the caller to specify a base64 encoded version of what should be returned via stdout, -// GO_TEST_RETURN_CODE flag allows the caller to specify what the return code should be, and -// GO_TEST_DELAY flag allows the caller to inject a delay before the function returns. -// GO_TEST_RETURN_PADDING_LENGTH flag allows the caller to specify how many bytes to return in total, -// if GO_TEST_RETURN_VALUE does not use all the bytes, the end is padded with NUL data -func TestShellProcess(t *testing.T) { - if os.Getenv("GO_TEST") != "1" { - return - } - // Print out the test value to stdout - returnString, _ := b64.StdEncoding.DecodeString(os.Getenv("GO_TEST_RETURN_VALUE")) - fmt.Fprintf(os.Stdout, string(returnString)) - if os.Getenv("GO_TEST_RETURN_PADDING_LENGTH") != "" { - padLength, _ := strconv.Atoi(os.Getenv("GO_TEST_RETURN_PADDING_LENGTH")) - padString := make([]byte, padLength-len(returnString)) - fmt.Fprintf(os.Stdout, string(padString)) - } - code, err := strconv.Atoi(os.Getenv("GO_TEST_RETURN_CODE")) - if err != nil { - code = -1 - } - // Pause for some amount of time - delay, err := time.ParseDuration(os.Getenv("GO_TEST_DELAY")) - if err == nil { - time.Sleep(delay) - } - os.Exit(code) -} diff --git a/utils/utils.go b/utils/utils.go index cff51be90..3f8387353 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -46,11 +46,6 @@ const ( PrepOutdated models.NodePrepStatus = "outdated" PrepPreConfigured models.NodePrepStatus = "preconfigured" - Centos = "centos" - RHEL = "rhel" - Ubuntu = "ubuntu" - Debian = "debian" - REDACTED = "" // NAS protocols @@ -784,11 +779,6 @@ func GetRegexSubmatches(r *regexp.Regexp, s string) map[string]string { return paramsMap } -// Detect if code is running in a container or not -func RunningInContainer() bool { - return os.Getenv("CSI_ENDPOINT") != "" -} - func Max(x, y int64) int64 { if x > y { return x