diff --git a/base_test.go b/base_test.go index 0ad548d..3453927 100644 --- a/base_test.go +++ b/base_test.go @@ -381,7 +381,7 @@ func (bmh *baseMockHelper) SCSIWaitUdevSymlinkErr( return bmh.SCSIWaitUdevSymlinkCall(m).Return(mh.ErrTest) } -func BaseConnectorCleanDeviceMock(mock *baseMockHelper, +func BaseConnectorCleanMultiPathDeviceMock(mock *baseMockHelper, scsi *intscsi.MockSCSI, mp *intmultipath.MockMultipath, ) { mock.SCSIGetDMDeviceByChildrenCallDevices = []string{ @@ -405,8 +405,22 @@ func BaseConnectorCleanDeviceMock(mock *baseMockHelper, mock.MultipathDelPathOK(mp) } +func BaseConnectorCleanDeviceMock(mock *baseMockHelper, + scsi *intscsi.MockSCSI, +) { + mock.SCSIGetDMDeviceByChildrenCallDevices = []string{ + mh.ValidDeviceName, mh.ValidDeviceName2, + } + mock.SCSIGetDMDeviceByChildrenErr(scsi) + + mock.SCSIDeleteSCSIDeviceByNameCallName = mh.ValidDeviceName + mock.SCSIDeleteSCSIDeviceByNameOK(scsi) + mock.SCSIDeleteSCSIDeviceByNameCallName = mh.ValidDeviceName2 + mock.SCSIDeleteSCSIDeviceByNameOK(scsi) +} + func BaserConnectorDisconnectDevicesByDeviceNameMock(mock *baseMockHelper, - scsi *intscsi.MockSCSI, mp *intmultipath.MockMultipath, + scsi *intscsi.MockSCSI, ) { mock.SCSIIsDeviceExistCallDevice = mh.ValidDMName mock.SCSIIsDeviceExistOKReturn = true @@ -424,5 +438,5 @@ func BaserConnectorDisconnectDevicesByDeviceNameMock(mock *baseMockHelper, mock.SCSIGetDevicesByWWNOKReturn = mh.ValidDevices mock.SCSIGetDevicesByWWNOK(scsi) - BaseConnectorCleanDeviceMock(mock, scsi, mp) + BaseConnectorCleanDeviceMock(mock, scsi) } diff --git a/fc_test.go b/fc_test.go index 86a789b..6288383 100644 --- a/fc_test.go +++ b/fc_test.go @@ -203,7 +203,11 @@ func cleanConnectionMock(mock *baseMockHelper, mock.SCSIGetDeviceNameByHCTLCallH = validHCTL1Target1 mock.SCSIGetDeviceNameByHCTLErr(scsi).AnyTimes() - BaseConnectorCleanDeviceMock(mock, scsi, multipath) + if mock.MultipathIsDaemonRunningOKReturn { + BaseConnectorCleanMultiPathDeviceMock(mock, scsi, multipath) + } else { + BaseConnectorCleanDeviceMock(mock, scsi) + } } func TestFCConnector_ConnectVolume(t *testing.T) { diff --git a/gobrick_test.go b/gobrick_test.go index 9747348..c714b66 100644 --- a/gobrick_test.go +++ b/gobrick_test.go @@ -33,6 +33,7 @@ var ( validLunNumber = 199 validSCSIHost1 = "34" validSCSIHost2 = "35" + validNQN = "11aaa111111111a11a111a1111aa1111" validHostOnlyHCTL1 = scsi.HCTL{Host: validSCSIHost1, Channel: "-", Target: "-", Lun: "-"} validHostOnlyHCTL2 = scsi.HCTL{Host: validSCSIHost2, Channel: "-", Target: "-", Lun: "-"} validHCTL1 = scsi.HCTL{ diff --git a/iscsi_test.go b/iscsi_test.go index b494efb..295304f 100644 --- a/iscsi_test.go +++ b/iscsi_test.go @@ -22,6 +22,8 @@ import ( "testing" "time" + "github.com/dell/gobrick/internal/powerpath" + "github.com/dell/gobrick/internal/mockhelper" intmultipath "github.com/dell/gobrick/internal/multipath" intscsi "github.com/dell/gobrick/internal/scsi" @@ -91,6 +93,7 @@ type iscsiFields struct { scsi *intscsi.MockSCSI iscsiLib *wrp.MockISCSILib filePath *wrp.MockLimitedFilepath + powerpath *powerpath.MockPowerpath manualSessionManagement bool waitDeviceTimeout time.Duration @@ -106,12 +109,14 @@ func getDefaultISCSIFields(ctrl *gomock.Controller) iscsiFields { bc := con.baseConnector mpMock := intmultipath.NewMockMultipath(ctrl) scsiMock := intscsi.NewMockSCSI(ctrl) + ppath := powerpath.NewMockPowerpath(ctrl) bc.multipath = mpMock bc.scsi = scsiMock return iscsiFields{ baseConnector: bc, multipath: mpMock, scsi: scsiMock, + powerpath: ppath, iscsiLib: wrp.NewMockISCSILib(ctrl), filePath: wrp.NewMockLimitedFilepath(ctrl), manualSessionManagement: con.manualSessionManagement, @@ -394,6 +399,7 @@ func TestISCSIConnector_ConnectVolume(t *testing.T) { baseConnector: tt.fields.baseConnector, multipath: tt.fields.multipath, scsi: tt.fields.scsi, + powerpath: tt.fields.powerpath, iscsiLib: tt.fields.iscsiLib, manualSessionManagement: tt.fields.manualSessionManagement, waitDeviceTimeout: tt.fields.waitDeviceTimeout, @@ -467,6 +473,7 @@ func TestISCSIConnector_GetInitiatorName(t *testing.T) { c := &ISCSIConnector{ baseConnector: tt.fields.baseConnector, multipath: tt.fields.multipath, + powerpath: tt.fields.powerpath, scsi: tt.fields.scsi, iscsiLib: tt.fields.iscsiLib, manualSessionManagement: tt.fields.manualSessionManagement, @@ -556,7 +563,7 @@ func TestISCSIConnector_DisconnectVolume(t *testing.T) { mock.SCSIGetDeviceNameByHCTLCallH = validHCTL2 mock.SCSIGetDeviceNameByHCTLOK(fields.scsi) - BaseConnectorCleanDeviceMock(&mock, fields.scsi, fields.multipath) + BaseConnectorCleanDeviceMock(&mock, fields.scsi) }, args: defaultArgs, wantErr: false, @@ -567,6 +574,7 @@ func TestISCSIConnector_DisconnectVolume(t *testing.T) { c := &ISCSIConnector{ baseConnector: tt.fields.baseConnector, multipath: tt.fields.multipath, + powerpath: tt.fields.powerpath, scsi: tt.fields.scsi, iscsiLib: tt.fields.iscsiLib, manualSessionManagement: tt.fields.manualSessionManagement, @@ -613,7 +621,7 @@ func TestISCSIConnector_DisconnectVolumeByDeviceName(t *testing.T) { name: "ok", fields: getDefaultISCSIFields(ctrl), stateSetter: func(fields iscsiFields) { - BaserConnectorDisconnectDevicesByDeviceNameMock(&mock, fields.scsi, fields.multipath) + BaserConnectorDisconnectDevicesByDeviceNameMock(&mock, fields.scsi) }, args: defaultArgs, wantErr: false, @@ -624,6 +632,7 @@ func TestISCSIConnector_DisconnectVolumeByDeviceName(t *testing.T) { c := &ISCSIConnector{ baseConnector: tt.fields.baseConnector, multipath: tt.fields.multipath, + powerpath: tt.fields.powerpath, scsi: tt.fields.scsi, iscsiLib: tt.fields.iscsiLib, manualSessionManagement: tt.fields.manualSessionManagement, diff --git a/nvme.go b/nvme.go index e321afc..41820e8 100644 --- a/nvme.go +++ b/nvme.go @@ -279,13 +279,13 @@ func (c *NVMeConnector) cleanConnection(ctx context.Context, force bool, info NV } var devicePath string var namespace string - logger.Debug(ctx, "DevicePathsAndNamespaces", DevicePathsAndNamespaces) + log.Debugf("DevicePathsAndNamespaces: %s", DevicePathsAndNamespaces) for _, DevicePathAndNamespace := range DevicePathsAndNamespaces { devicePath = DevicePathAndNamespace.DevicePath namespace = DevicePathAndNamespace.Namespace nguid, newnamespace, _ := c.nvmeLib.GetNVMeDeviceData(devicePath) - logger.Debug(ctx, "nguid, wwn, newnamespace, namespace", nguid, wwn, newnamespace, namespace) - if c.wwnMatches(ctx, nguid, wwn) && namespace == newnamespace { + log.Debugf("nguid: %S, wwn: %s, newnamespace: %s, namespace: %s", nguid, wwn, newnamespace, namespace) + if c.wwnMatches(nguid, wwn) && namespace == newnamespace { devices = append(devices, devicePath) } } @@ -496,13 +496,13 @@ func (c *NVMeConnector) discoverDevice(ctx context.Context, wg *sync.WaitGroup, var devicePaths []string var devicePath string var namespace string - logger.Debug(ctx, "DevicePathsAndNamespaces", DevicePathsAndNamespaces, "retryCount", retryCount) + log.Debugf("DevicePathsAndNamespaces %+v retryCount %d", DevicePathsAndNamespaces, retryCount) for _, DevicePathAndNamespace := range DevicePathsAndNamespaces { devicePath = DevicePathAndNamespace.DevicePath namespace = DevicePathAndNamespace.Namespace nguid, newnamespace, _ := c.nvmeLib.GetNVMeDeviceData(devicePath) - logger.Debug(ctx, "nguid, wwn, newnamespace, namespace", nguid, wwn, newnamespace, namespace) - if c.wwnMatches(ctx, nguid, wwn) && namespace == newnamespace { + log.Debugf("nguid %s, wwn %s, newnamespace %s, namespace %s", nguid, wwn, newnamespace, namespace) + if c.wwnMatches(nguid, wwn) && namespace == newnamespace { devicePaths = append(devicePaths, devicePath) nguidResult = nguid // using two nvme devices for each volume for the multipath discovery @@ -526,19 +526,21 @@ func (c *NVMeConnector) discoverDevice(ctx context.Context, wg *sync.WaitGroup, result <- devicePathResult } -func (c *NVMeConnector) wwnMatches(ctx context.Context, nguid, wwn string) bool { +func (c *NVMeConnector) wwnMatches(nguid, wwn string) bool { /* - Sample wwn : naa.68ccf098001111a2222b3d4444a1b23c - wwn1 : 1111a2222b3d4444 - wwn2 : a1b23c - - Sample nguid : 1111a2222b3d44448ccf096800a1b23c - - / pmax: - nguid: 12635330303134340000976000012000 - wwn: 60000970000120001263533030313434 - nguid: wwn[last16] + wwn[1:6] + wwn[0] + wwn[7:15] - 1263533030313434 + 000097 + 6 + 000012000 + Sample wwn : naa.68ccf098001111a2222b3d4444a1b23c + wwn1 : 1111a2222b3d4444 + wwn2 : a1b23c + + Sample nguid : 1111a2222b3d44448ccf096800a1b23c + + / pmax: + nguid: 12635330303134340000976000012000 + wwn: 60000970000120001263533030313434 + 11aaa111111111a11a111a1111aa1111 + 1a111a1111aa1111 1aaa11 1 1111111a1 + nguid: wwn[last16] + wwn[1:6] + wwn[0] + wwn[7:15] + 1263533030313434 + 000097 + 6 + 000012000 */ if len(wwn) < 32 { return false @@ -555,7 +557,7 @@ func (c *NVMeConnector) wwnMatches(ctx context.Context, nguid, wwn string) bool // pmax wwn1 = wwn[16:] wwn2 = wwn[1:7] - logger.Info(ctx, "Powermax:", wwn1, wwn2, nguid, strings.HasPrefix(nguid, wwn1+wwn2)) + log.Infof("Powermax: %s %s %s %t", wwn1, wwn2, nguid, strings.HasPrefix(nguid, wwn1+wwn2)) if strings.HasPrefix(nguid, wwn1+wwn2) { return true } diff --git a/nvme_test.go b/nvme_test.go new file mode 100644 index 0000000..fe3297b --- /dev/null +++ b/nvme_test.go @@ -0,0 +1,193 @@ +/* +Copyright © 2020-2022 Dell Inc. or its subsidiaries. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package gobrick + +import ( + "context" + "reflect" + "testing" + "time" + + "github.com/dell/gonvme" + + intmultipath "github.com/dell/gobrick/internal/multipath" + intscsi "github.com/dell/gobrick/internal/scsi" + wrp "github.com/dell/gobrick/internal/wrappers" + "github.com/golang/mock/gomock" + "golang.org/x/sync/semaphore" + "golang.org/x/sync/singleflight" +) + +var ( + validNVMEPortal1 = "1.1.1.1:3260" + validNVMETarget1 = "nqn.2014-08.org.nvmexpress:uuid:csi_master" + validNVMEPortal2 = "1.1.1.1:3260" + validNVMETarget2 = "nqn.2014-08.org.nvmexpress:uuid:csi_worker" + validNVMETargetInfo1 = NVMeTargetInfo{ + Portal: validNVMEPortal1, + Target: validNVMETarget1, + } + validNVMETargetInfo2 = NVMeTargetInfo{ + Portal: validNVMEPortal2, + Target: validNVMETarget2, + } + validNVMEVolumeInfo = NVMeVolumeInfo{ + Targets: []NVMeTargetInfo{validNVMETargetInfo1, validNVMETargetInfo2}, + WWN: validNQN, + } + + validLibNVMETarget1 = gonvme.NVMeTarget{ + TargetNqn: validNVMETarget1, + Portal: validNVMEPortal1, + } + + validLibNVMETarget2 = gonvme.NVMeTarget{ + TargetNqn: validNVMETarget2, + Portal: validNVMEPortal2, + } + + validNVMEInitiatorName = "nqn.2014-08.org.nvmexpress:uuid:csi_worker:e16da41ba075" + + validLibNVMESession1 = gonvme.NVMESession{ + Target: validNVMETarget1, + Portal: validNVMEPortal1, + Name: "nvme1", + NVMESessionState: "live", + NVMETransportName: "tcp", + } + validLibNVMESession2 = gonvme.NVMESession{ + Target: validNVMETarget2, + Portal: validNVMEPortal2, + Name: "nvme2", + NVMESessionState: "live", + NVMETransportName: "tcp", + } + validLibNVMESessions = []gonvme.NVMESession{validLibNVMESession1, validLibNVMESession2} +) + +type NVMEFields struct { + baseConnector *baseConnector + multipath *intmultipath.MockMultipath + scsi *intscsi.MockSCSI + nvmeLib *gonvme.MockNVMe + filePath *wrp.MockLimitedFilepath + manualSessionManagement bool + waitDeviceTimeout time.Duration + waitDeviceRegisterTimeout time.Duration + failedSessionMinimumLoginRetryInterval time.Duration + loginLock *rateLock + limiter *semaphore.Weighted + singleCall *singleflight.Group +} + +func getDefaultNVMEFields(ctrl *gomock.Controller) NVMEFields { + con := NewNVMeConnector(NVMeConnectorParams{}) + bc := con.baseConnector + mpMock := intmultipath.NewMockMultipath(ctrl) + scsiMock := intscsi.NewMockSCSI(ctrl) + nvmeMock := gonvme.NewMockNVMe(map[string]string{}) + bc.multipath = mpMock + bc.scsi = scsiMock + return NVMEFields{ + baseConnector: bc, + multipath: mpMock, + scsi: scsiMock, + nvmeLib: nvmeMock, + filePath: wrp.NewMockLimitedFilepath(ctrl), + manualSessionManagement: con.manualSessionManagement, + waitDeviceTimeout: con.waitDeviceTimeout, + waitDeviceRegisterTimeout: con.waitDeviceRegisterTimeout, + failedSessionMinimumLoginRetryInterval: con.waitDeviceTimeout, + loginLock: con.loginLock, + limiter: con.limiter, + singleCall: con.singleCall, + } +} + +func TestNVME_Connector_ConnectVolume(t *testing.T) { + type args struct { + ctx context.Context + info NVMeVolumeInfo + } + + ctx := context.Background() + defaultArgs := args{ctx: ctx, info: validNVMEVolumeInfo} + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mock := baseMockHelper{ + Ctx: ctx, + } + + tests := []struct { + name string + fields NVMEFields + args args + stateSetter func(fields NVMEFields) + want Device + wantErr bool + isFC bool + }{ + { + name: "empty request", + fields: getDefaultNVMEFields(ctrl), + stateSetter: func(_ NVMEFields) {}, + args: args{ctx: ctx, info: NVMeVolumeInfo{}}, + want: Device{}, + wantErr: true, + isFC: false, + }, + { + name: "not found-single device", + fields: getDefaultNVMEFields(ctrl), + stateSetter: func(fields NVMEFields) { + mock.MultipathIsDaemonRunningOKReturn = false + mock.MultipathIsDaemonRunningOK(fields.multipath) + }, + args: defaultArgs, + want: Device{}, + wantErr: true, + isFC: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &NVMeConnector{ + baseConnector: tt.fields.baseConnector, + multipath: tt.fields.multipath, + scsi: tt.fields.scsi, + nvmeLib: tt.fields.nvmeLib, + manualSessionManagement: tt.fields.manualSessionManagement, + waitDeviceTimeout: tt.fields.waitDeviceTimeout, + waitDeviceRegisterTimeout: tt.fields.waitDeviceRegisterTimeout, + loginLock: tt.fields.loginLock, + limiter: tt.fields.limiter, + singleCall: tt.fields.singleCall, + filePath: tt.fields.filePath, + } + tt.stateSetter(tt.fields) + got, err := c.ConnectVolume(tt.args.ctx, tt.args.info, tt.isFC) + if (err != nil) != tt.wantErr { + t.Errorf("ConnectVolume() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ConnectVolume() got = %v, want %v", got, tt.want) + } + }) + } +}