diff --git a/mocks/mock_storage_drivers/mock_ontap/mock_api.go b/mocks/mock_storage_drivers/mock_ontap/mock_api.go index 60fa463a6..feb8651cd 100644 --- a/mocks/mock_storage_drivers/mock_ontap/mock_api.go +++ b/mocks/mock_storage_drivers/mock_ontap/mock_api.go @@ -1231,6 +1231,21 @@ func (mr *MockOntapAPIMockRecorder) NVMeSubsystemRemoveNamespace(arg0, arg1, arg return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NVMeSubsystemRemoveNamespace", reflect.TypeOf((*MockOntapAPI)(nil).NVMeSubsystemRemoveNamespace), arg0, arg1, arg2) } +// NetFcpInterfaceGetDataLIFs mocks base method. +func (m *MockOntapAPI) NetFcpInterfaceGetDataLIFs(arg0 context.Context, arg1 string) ([]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetFcpInterfaceGetDataLIFs", arg0, arg1) + ret0, _ := ret[0].([]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetFcpInterfaceGetDataLIFs indicates an expected call of NetFcpInterfaceGetDataLIFs. +func (mr *MockOntapAPIMockRecorder) NetFcpInterfaceGetDataLIFs(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetFcpInterfaceGetDataLIFs", reflect.TypeOf((*MockOntapAPI)(nil).NetFcpInterfaceGetDataLIFs), arg0, arg1) +} + // NetInterfaceGetDataLIFs mocks base method. func (m *MockOntapAPI) NetInterfaceGetDataLIFs(arg0 context.Context, arg1 string) ([]string, error) { m.ctrl.T.Helper() diff --git a/mocks/mock_storage_drivers/mock_ontap/mock_ontap_rest_interface.go b/mocks/mock_storage_drivers/mock_ontap/mock_ontap_rest_interface.go index 24f41dedd..9099a9cb7 100644 --- a/mocks/mock_storage_drivers/mock_ontap/mock_ontap_rest_interface.go +++ b/mocks/mock_storage_drivers/mock_ontap/mock_ontap_rest_interface.go @@ -1306,6 +1306,21 @@ func (mr *MockRestClientInterfaceMockRecorder) NVMeSubsystemRemoveNamespace(arg0 return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NVMeSubsystemRemoveNamespace", reflect.TypeOf((*MockRestClientInterface)(nil).NVMeSubsystemRemoveNamespace), arg0, arg1, arg2) } +// NetFcpInterfaceGetDataLIFs mocks base method. +func (m *MockRestClientInterface) NetFcpInterfaceGetDataLIFs(arg0 context.Context, arg1 string) ([]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetFcpInterfaceGetDataLIFs", arg0, arg1) + ret0, _ := ret[0].([]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetFcpInterfaceGetDataLIFs indicates an expected call of NetFcpInterfaceGetDataLIFs. +func (mr *MockRestClientInterfaceMockRecorder) NetFcpInterfaceGetDataLIFs(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetFcpInterfaceGetDataLIFs", reflect.TypeOf((*MockRestClientInterface)(nil).NetFcpInterfaceGetDataLIFs), arg0, arg1) +} + // NetInterfaceGetDataLIFs mocks base method. func (m *MockRestClientInterface) NetInterfaceGetDataLIFs(arg0 context.Context, arg1 string) ([]string, error) { m.ctrl.T.Helper() diff --git a/mocks/mock_storage_drivers/mock_ontap/mock_ontap_zapi_interface.go b/mocks/mock_storage_drivers/mock_ontap/mock_ontap_zapi_interface.go index e9de5ec04..a3347351e 100644 --- a/mocks/mock_storage_drivers/mock_ontap/mock_ontap_zapi_interface.go +++ b/mocks/mock_storage_drivers/mock_ontap/mock_ontap_zapi_interface.go @@ -1179,6 +1179,21 @@ func (mr *MockZapiClientInterfaceMockRecorder) LunUnmap(arg0, arg1 any) *gomock. return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LunUnmap", reflect.TypeOf((*MockZapiClientInterface)(nil).LunUnmap), arg0, arg1) } +// NetFcpInterfaceGetDataLIFs mocks base method. +func (m *MockZapiClientInterface) NetFcpInterfaceGetDataLIFs(arg0 context.Context, arg1 string) ([]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetFcpInterfaceGetDataLIFs", arg0, arg1) + ret0, _ := ret[0].([]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetFcpInterfaceGetDataLIFs indicates an expected call of NetFcpInterfaceGetDataLIFs. +func (mr *MockZapiClientInterfaceMockRecorder) NetFcpInterfaceGetDataLIFs(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetFcpInterfaceGetDataLIFs", reflect.TypeOf((*MockZapiClientInterface)(nil).NetFcpInterfaceGetDataLIFs), arg0, arg1) +} + // NetInterfaceGet mocks base method. func (m *MockZapiClientInterface) NetInterfaceGet() (*azgo.NetInterfaceGetIterResponse, error) { m.ctrl.T.Helper() diff --git a/storage_drivers/ontap/api/abstraction.go b/storage_drivers/ontap/api/abstraction.go index 42ef08374..d6273dc60 100644 --- a/storage_drivers/ontap/api/abstraction.go +++ b/storage_drivers/ontap/api/abstraction.go @@ -144,6 +144,7 @@ type OntapAPI interface { GetSLMDataLifs(ctx context.Context, ips, reportingNodeNames []string) ([]string, error) NetInterfaceGetDataLIFs(ctx context.Context, protocol string) ([]string, error) + NetFcpInterfaceGetDataLIFs(ctx context.Context, protocol string) ([]string, error) NodeListSerialNumbers(ctx context.Context) ([]string, error) SnapshotRestoreVolume(ctx context.Context, snapshotName, sourceVolume string) error diff --git a/storage_drivers/ontap/api/abstraction_rest.go b/storage_drivers/ontap/api/abstraction_rest.go index 697fa10a2..87836406c 100644 --- a/storage_drivers/ontap/api/abstraction_rest.go +++ b/storage_drivers/ontap/api/abstraction_rest.go @@ -535,6 +535,10 @@ func (d OntapAPIREST) NetInterfaceGetDataLIFs(ctx context.Context, protocol stri return d.api.NetInterfaceGetDataLIFs(ctx, protocol) } +func (d OntapAPIREST) NetFcpInterfaceGetDataLIFs(ctx context.Context, protocol string) ([]string, error) { + return d.api.NetFcpInterfaceGetDataLIFs(ctx, protocol) +} + func (d OntapAPIREST) GetSVMAggregateNames(ctx context.Context) ([]string, error) { return d.api.SVMGetAggregateNames(ctx) } diff --git a/storage_drivers/ontap/api/abstraction_zapi.go b/storage_drivers/ontap/api/abstraction_zapi.go index 1371603ef..725faef45 100644 --- a/storage_drivers/ontap/api/abstraction_zapi.go +++ b/storage_drivers/ontap/api/abstraction_zapi.go @@ -1091,6 +1091,10 @@ func (d OntapAPIZAPI) NetInterfaceGetDataLIFs(ctx context.Context, protocol stri return d.api.NetInterfaceGetDataLIFs(ctx, protocol) } +func (d OntapAPIZAPI) NetFcpInterfaceGetDataLIFs(ctx context.Context, protocol string) ([]string, error) { + return d.api.NetFcpInterfaceGetDataLIFs(ctx, protocol) +} + func (d OntapAPIZAPI) GetSVMAggregateNames(_ context.Context) ([]string, error) { return d.api.SVMGetAggregateNames() } diff --git a/storage_drivers/ontap/api/ontap_rest.go b/storage_drivers/ontap/api/ontap_rest.go index 3983351b3..91e940cef 100644 --- a/storage_drivers/ontap/api/ontap_rest.go +++ b/storage_drivers/ontap/api/ontap_rest.go @@ -3159,6 +3159,40 @@ func (c RestClient) NetInterfaceGetDataLIFs(ctx context.Context, protocol string return dataLIFs, nil } +func (c RestClient) NetFcpInterfaceGetDataLIFs(ctx context.Context, protocol string) ([]string, error) { + if protocol == "" { + return nil, fmt.Errorf("missing protocol specification") + } + + params := networking.NewFcInterfaceCollectionGetParamsWithTimeout(c.httpClient.Timeout) + params.Context = ctx + params.HTTPClient = c.httpClient + + params.SvmUUID = utils.Ptr(c.svmUUID) + fields := []string{"wwpn", "state"} + params.SetFields(fields) + + lifResponse, err := c.api.Networking.FcInterfaceCollectionGet(params, c.authInfo) + if err != nil { + return nil, fmt.Errorf("error checking network interfaces; %v", err) + } + if lifResponse == nil { + return nil, fmt.Errorf("unexpected error checking network interfaces") + } + + dataLIFs := make([]string, 0) + for _, record := range lifResponse.Payload.FcInterfaceResponseInlineRecords { + if record.Wwpn != nil && record.State != nil && *record.State == models.IPInterfaceStateUp { + if record.Wwpn != nil { + dataLIFs = append(dataLIFs, *record.Wwpn) + } + } + } + + Logc(ctx).WithField("dataLIFs", dataLIFs).Debug("Data LIFs") + return dataLIFs, nil +} + // //////////////////////////////////////////////////////////////////////////// // JOB operations // //////////////////////////////////////////////////////////////////////////// diff --git a/storage_drivers/ontap/api/ontap_rest_interface.go b/storage_drivers/ontap/api/ontap_rest_interface.go index 101212b82..8ce5772b4 100644 --- a/storage_drivers/ontap/api/ontap_rest_interface.go +++ b/storage_drivers/ontap/api/ontap_rest_interface.go @@ -172,6 +172,7 @@ type RestClientInterface interface { // NetworkIPInterfacesList lists all IP interfaces NetworkIPInterfacesList(ctx context.Context) (*networking.NetworkIPInterfacesGetOK, error) NetInterfaceGetDataLIFs(ctx context.Context, protocol string) ([]string, error) + NetFcpInterfaceGetDataLIFs(ctx context.Context, protocol string) ([]string, error) // JobGet returns the job by ID JobGet(ctx context.Context, jobUUID string, fields []string) (*cluster.JobGetOK, error) // IsJobFinished lookus up the supplied JobLinkResponse's UUID to see if it's reached a terminal state diff --git a/storage_drivers/ontap/api/ontap_zapi.go b/storage_drivers/ontap/api/ontap_zapi.go index 031bde677..1e477c5eb 100644 --- a/storage_drivers/ontap/api/ontap_zapi.go +++ b/storage_drivers/ontap/api/ontap_zapi.go @@ -2984,6 +2984,37 @@ func (c Client) NetInterfaceGetDataLIFs(ctx context.Context, protocol string) ([ return dataLIFs, nil } +func (c Client) NetFcpInterfaceGet() (*azgo.FcpInterfaceGetIterResponse, error) { + response, err := azgo.NewFcpInterfaceGetIterRequest(). + SetMaxRecords(DefaultZapiRecords). + SetQuery(azgo.FcpInterfaceGetIterRequestQuery{ + FcpInterfaceInfoPtr: &azgo.FcpInterfaceInfoType{}, + }).ExecuteUsing(c.zr) + + return response, err +} + +func (c Client) NetFcpInterfaceGetDataLIFs(ctx context.Context, protocol string) ([]string, error) { + lifResponse, err := c.NetFcpInterfaceGet() + if err = azgo.GetError(ctx, lifResponse, err); err != nil { + return nil, fmt.Errorf("error checking network interfaces: %v", err) + } + + dataLIFs := make([]string, 0) + if lifResponse.Result.AttributesListPtr != nil { + for _, attrs := range lifResponse.Result.AttributesListPtr.FcpInterfaceInfoPtr { + dataLIFs = append(dataLIFs, attrs.PortName()) + } + } + + if len(dataLIFs) < 1 { + return []string{}, fmt.Errorf("no data LIFs meet the provided criteria (protocol: %s)", protocol) + } + + Logc(ctx).WithField("dataLIFs", dataLIFs).Debug("Data LIFs") + return dataLIFs, nil +} + // SystemGetVersion returns the system version // equivalent to filer::> version func (c Client) SystemGetVersion() (*azgo.SystemGetVersionResponse, error) { diff --git a/storage_drivers/ontap/api/ontap_zapi_interface.go b/storage_drivers/ontap/api/ontap_zapi_interface.go index f991629a0..191c59a2d 100644 --- a/storage_drivers/ontap/api/ontap_zapi_interface.go +++ b/storage_drivers/ontap/api/ontap_zapi_interface.go @@ -366,6 +366,7 @@ type ZapiClientInterface interface { // equivalent to filer::> net interface list, but only those LIFs that are operational NetInterfaceGet() (*azgo.NetInterfaceGetIterResponse, error) NetInterfaceGetDataLIFs(ctx context.Context, protocol string) ([]string, error) + NetFcpInterfaceGetDataLIFs(ctx context.Context, protocol string) ([]string, error) // SystemGetVersion returns the system version // equivalent to filer::> version SystemGetVersion() (*azgo.SystemGetVersionResponse, error) diff --git a/storage_drivers/ontap/ontap_common.go b/storage_drivers/ontap/ontap_common.go index 103277141..9af9ca84b 100644 --- a/storage_drivers/ontap/ontap_common.go +++ b/storage_drivers/ontap/ontap_common.go @@ -485,17 +485,23 @@ func getSVMState( } } - // Check dataLIF for iSCSI and NVMe protocols - if protocol != sa.FCP { - upDataLIFs, err := client.NetInterfaceGetDataLIFs(ctx, protocol) - if err != nil || len(upDataLIFs) == 0 { - if err != nil { - // Log error and keep going. - Logc(ctx).WithField("error", err).Warn("Error getting list of data LIFs from backend.") - } - // No data LIFs with state 'up' found. - return StateReasonDataLIFsDown, changeMap + upDataLIFs := make([]string, 0) + if protocol == sa.FCP { + // Check dataLIF for FC protocol + upDataLIFs, err = client.NetFcpInterfaceGetDataLIFs(ctx, protocol) + } else { + // Check dataLIF for iSCSI and NVMe protocols + upDataLIFs, err = client.NetInterfaceGetDataLIFs(ctx, protocol) + } + + if err != nil || len(upDataLIFs) == 0 { + if err != nil { + // Log error and keep going. + fields := LogFields{"error": err, "protocol": protocol} + Logc(ctx).WithFields(fields).Warn("Error getting list of data LIFs from backend.") } + // No data LIFs with state 'up' found. + return StateReasonDataLIFsDown, changeMap } // Get ONTAP version diff --git a/storage_drivers/ontap/ontap_common_test.go b/storage_drivers/ontap/ontap_common_test.go index 1a85ced57..52464094a 100644 --- a/storage_drivers/ontap/ontap_common_test.go +++ b/storage_drivers/ontap/ontap_common_test.go @@ -6133,7 +6133,6 @@ func TestGetSVMState(t *testing.T) { dataLIFs := []string{"1.2.3.4"} derivedPools := []string{"aggr1"} existingPools := []string{"aggr1"} - protocol := sa.ISCSI // Format for every test: // Calls needed. @@ -6144,7 +6143,7 @@ func TestGetSVMState(t *testing.T) { // a: API.GetSVMState returns error mockAPI.EXPECT().GetSVMState(ctx).Return("TestStateInvalid", fmt.Errorf("GetSVMState returned error")) - state, code := getSVMState(ctx, mockAPI, protocol, derivedPoolsNil, aggrsNil...) + state, code := getSVMState(ctx, mockAPI, sa.ISCSI, derivedPoolsNil, aggrsNil...) assert.Equal(t, StateReasonSVMUnreachable, state, "state returned should be TestStateUnknown") assert.False(t, code.Contains(storage.BackendStatePoolsChange), "Should not be pool change") assert.False(t, code.Contains(storage.BackendStateAPIVersionChange), "Should not be API version change") @@ -6153,7 +6152,7 @@ func TestGetSVMState(t *testing.T) { // a: SVM is stopped mockAPI.EXPECT().GetSVMState(ctx).Return(models.SvmStateStopped, nil).Times(1) - state, code = getSVMState(ctx, mockAPI, protocol, derivedPoolsNil, aggrsNil...) + state, code = getSVMState(ctx, mockAPI, sa.ISCSI, derivedPoolsNil, aggrsNil...) assert.Equal(t, StateReasonSVMStopped, state, "state should be SVM stopped") assert.False(t, code.Contains(storage.BackendStatePoolsChange), "Should not be pool change") assert.False(t, code.Contains(storage.BackendStateAPIVersionChange), "Should not be API version change") @@ -6167,7 +6166,7 @@ func TestGetSVMState(t *testing.T) { mockAPI.EXPECT().APIVersion(ctx, true).Return("9.14.1", nil).Times(1) mockAPI.EXPECT().APIVersion(ctx, false).Return("9.14.1", nil).Times(1) - state, code = getSVMState(ctx, mockAPI, protocol, derivedPoolsNil, aggrsNil...) + state, code = getSVMState(ctx, mockAPI, sa.ISCSI, derivedPoolsNil, aggrsNil...) assert.False(t, code.Contains(storage.BackendStateReasonChange), "Should not be reason change") assert.False(t, code.Contains(storage.BackendStatePoolsChange), "Should not be pool change") assert.False(t, code.Contains(storage.BackendStateAPIVersionChange), "Should not be API version change") @@ -6175,7 +6174,7 @@ func TestGetSVMState(t *testing.T) { // b: client.GetSVMAggregateNames returns empty list mockAPI.EXPECT().GetSVMAggregateNames(ctx).Return(nil, nil).Times(1) - state, code = getSVMState(ctx, mockAPI, protocol, []string{"aggr1"}, aggrsNil...) + state, code = getSVMState(ctx, mockAPI, sa.ISCSI, []string{"aggr1"}, aggrsNil...) assert.False(t, code.Contains(storage.BackendStateReasonChange), "Should not be reason change") assert.False(t, code.Contains(storage.BackendStateAPIVersionChange), "Should not be API version change") assert.True(t, code.Contains(storage.BackendStatePoolsChange), "Should be a pool change") @@ -6184,7 +6183,7 @@ func TestGetSVMState(t *testing.T) { // c: client.GetSVMAggregateNames returns non-empty list, config.Aggregate is missing mockAPI.EXPECT().GetSVMAggregateNames(ctx).Return([]string{"aggr1", "aggr2"}, nil).Times(1) - state, code = getSVMState(ctx, mockAPI, protocol, []string{"aggr1"}, []string{"aggr3"}...) + state, code = getSVMState(ctx, mockAPI, sa.ISCSI, []string{"aggr1"}, []string{"aggr3"}...) assert.False(t, code.Contains(storage.BackendStateReasonChange), "Should not be reason change") assert.False(t, code.Contains(storage.BackendStateAPIVersionChange), "Should not be API version change") assert.True(t, code.Contains(storage.BackendStatePoolsChange), "Should be a pool change") @@ -6196,7 +6195,7 @@ func TestGetSVMState(t *testing.T) { mockAPI.EXPECT().APIVersion(ctx, true).Return("9.14.1", nil).Times(1) mockAPI.EXPECT().APIVersion(ctx, false).Return("9.14.1", nil).Times(1) - state, code = getSVMState(ctx, mockAPI, protocol, []string{"aggr1"}, []string{"aggr1"}...) + state, code = getSVMState(ctx, mockAPI, sa.ISCSI, []string{"aggr1"}, []string{"aggr1"}...) assert.False(t, code.Contains(storage.BackendStateReasonChange), "Should not be reason change") assert.False(t, code.Contains(storage.BackendStateAPIVersionChange), "Should not be API version change") assert.False(t, code.Contains(storage.BackendStatePoolsChange), "Should be no pool change") @@ -6208,7 +6207,7 @@ func TestGetSVMState(t *testing.T) { mockAPI.EXPECT().APIVersion(ctx, true).Return("9.14.1", nil).Times(1) mockAPI.EXPECT().APIVersion(ctx, false).Return("9.14.1", nil).Times(1) - state, code = getSVMState(ctx, mockAPI, protocol, []string{"aggr1"}, "") + state, code = getSVMState(ctx, mockAPI, sa.ISCSI, []string{"aggr1"}, "") assert.False(t, code.Contains(storage.BackendStateReasonChange), "Should not be reason change") assert.False(t, code.Contains(storage.BackendStateAPIVersionChange), "Should not be API version change") assert.False(t, code.Contains(storage.BackendStatePoolsChange), "Should be no pool change") @@ -6220,7 +6219,7 @@ func TestGetSVMState(t *testing.T) { // a: error getting data LIFs mockAPI.EXPECT().NetInterfaceGetDataLIFs(ctx, gomock.Any()).Return(dataLIFs, fmt.Errorf("API call returned error")).Times(1) - state, code = getSVMState(ctx, mockAPI, protocol, existingPools) + state, code = getSVMState(ctx, mockAPI, sa.ISCSI, existingPools) assert.False(t, code.Contains(storage.BackendStateReasonChange), "Should not be reason change") assert.False(t, code.Contains(storage.BackendStateAPIVersionChange), "Should not be API version change") assert.False(t, code.Contains(storage.BackendStatePoolsChange), "Should be no pool change") @@ -6229,7 +6228,7 @@ func TestGetSVMState(t *testing.T) { // b: no data LIFs in up state mockAPI.EXPECT().NetInterfaceGetDataLIFs(ctx, gomock.Any()).Return(nil, nil).Times(1) - state, code = getSVMState(ctx, mockAPI, protocol, existingPools) + state, code = getSVMState(ctx, mockAPI, sa.ISCSI, existingPools) assert.False(t, code.Contains(storage.BackendStateReasonChange), "Should not be reason change") assert.False(t, code.Contains(storage.BackendStateAPIVersionChange), "Should not be API version change") assert.False(t, code.Contains(storage.BackendStatePoolsChange), "Should be no pool change") @@ -6240,7 +6239,39 @@ func TestGetSVMState(t *testing.T) { mockAPI.EXPECT().APIVersion(ctx, true).Return("9.14.1", nil).Times(1) mockAPI.EXPECT().APIVersion(ctx, false).Return("9.14.1", nil).Times(1) - state, code = getSVMState(ctx, mockAPI, protocol, existingPools) + state, code = getSVMState(ctx, mockAPI, sa.ISCSI, existingPools) + assert.False(t, code.Contains(storage.BackendStateReasonChange), "Should not be reason change") + assert.False(t, code.Contains(storage.BackendStateAPIVersionChange), "Should not be API version change") + assert.False(t, code.Contains(storage.BackendStatePoolsChange), "Should be no pool change") + assert.Equal(t, "", state, "Reason should be empty") + + mockAPI.EXPECT().NetInterfaceGetDataLIFs(ctx, gomock.Any()).Return(dataLIFs, nil).AnyTimes() + + // d: error getting FC data LIFs + mockAPI.EXPECT().NetFcpInterfaceGetDataLIFs(ctx, gomock.Any()).Return(dataLIFs, + fmt.Errorf("API call returned error")).Times(1) + + state, code = getSVMState(ctx, mockAPI, sa.FCP, existingPools) + assert.False(t, code.Contains(storage.BackendStateReasonChange), "Should not be reason change") + assert.False(t, code.Contains(storage.BackendStateAPIVersionChange), "Should not be API version change") + assert.False(t, code.Contains(storage.BackendStatePoolsChange), "Should be no pool change") + assert.Equal(t, StateReasonDataLIFsDown, state, "Reason should be equal to StateReasonDataLIFsDown") + + // e: no FC data LIFs in up state + mockAPI.EXPECT().NetFcpInterfaceGetDataLIFs(ctx, gomock.Any()).Return(nil, nil).Times(1) + + state, code = getSVMState(ctx, mockAPI, sa.FCP, existingPools) + assert.False(t, code.Contains(storage.BackendStateReasonChange), "Should not be reason change") + assert.False(t, code.Contains(storage.BackendStateAPIVersionChange), "Should not be API version change") + assert.False(t, code.Contains(storage.BackendStatePoolsChange), "Should be no pool change") + assert.Equal(t, StateReasonDataLIFsDown, state, "Reason should be equal to StateReasonDataLIFsDown") + + // f: all well with FC. + mockAPI.EXPECT().NetFcpInterfaceGetDataLIFs(ctx, gomock.Any()).Return(dataLIFs, nil).Times(1) + mockAPI.EXPECT().APIVersion(ctx, true).Return("9.14.1", nil).Times(1) + mockAPI.EXPECT().APIVersion(ctx, false).Return("9.14.1", nil).Times(1) + + state, code = getSVMState(ctx, mockAPI, sa.FCP, existingPools) assert.False(t, code.Contains(storage.BackendStateReasonChange), "Should not be reason change") assert.False(t, code.Contains(storage.BackendStateAPIVersionChange), "Should not be API version change") assert.False(t, code.Contains(storage.BackendStatePoolsChange), "Should be no pool change") @@ -6253,7 +6284,7 @@ func TestGetSVMState(t *testing.T) { mockAPI.EXPECT().APIVersion(ctx, false).Return("9.15.1", nil).Times(1) mockAPI.EXPECT().APIVersion(ctx, true).Return("9.14.1", nil).Times(1) - state, code = getSVMState(ctx, mockAPI, protocol, existingPools) + state, code = getSVMState(ctx, mockAPI, sa.ISCSI, existingPools) assert.False(t, code.Contains(storage.BackendStateReasonChange), "Should not be reason change") assert.False(t, code.Contains(storage.BackendStatePoolsChange), "Should be no pool change") assert.True(t, code.Contains(storage.BackendStateAPIVersionChange), "Should reflect change in ONTAP version") @@ -6262,20 +6293,20 @@ func TestGetSVMState(t *testing.T) { mockAPI.EXPECT().APIVersion(ctx, false).Return("9.14.1", nil).Times(1) mockAPI.EXPECT().APIVersion(ctx, true).Return("9.14.1", nil).Times(1) - state, code = getSVMState(ctx, mockAPI, protocol, existingPools) + state, code = getSVMState(ctx, mockAPI, sa.ISCSI, existingPools) assert.False(t, code.Contains(storage.BackendStateAPIVersionChange), "Should reflect NO change in ONTAP version") // c: current ONTAP version < cached ONTAP version mockAPI.EXPECT().APIVersion(ctx, false).Return("9.13.1", nil).Times(1) mockAPI.EXPECT().APIVersion(ctx, true).Return("9.14.1", nil).Times(1) - state, code = getSVMState(ctx, mockAPI, protocol, existingPools) + state, code = getSVMState(ctx, mockAPI, sa.ISCSI, existingPools) assert.True(t, code.Contains(storage.BackendStateAPIVersionChange), "Should reflect change in ONTAP version") // d: error in fetching the api version mockAPI.EXPECT().APIVersion(ctx, true).Return("", fmt.Errorf("API call returned error")).Times(1) - state, code = getSVMState(ctx, mockAPI, protocol, existingPools) + state, code = getSVMState(ctx, mockAPI, sa.ISCSI, existingPools) assert.False(t, code.Contains(storage.BackendStateAPIVersionChange), "Should not be API version change") // Creating a ZAPI mock client, to test the same scenario as above, just specific to ZAPI. @@ -6288,7 +6319,7 @@ func TestGetSVMState(t *testing.T) { mockZapiClient.EXPECT().SystemGetOntapiVersion(ctx, true).Return("1.241", nil) mockZapiClient.EXPECT().SystemGetOntapiVersion(ctx, false).Return("1.251", nil) - state, code = getSVMState(ctx, mockONTAPZAPI, protocol, existingPools) + state, code = getSVMState(ctx, mockONTAPZAPI, sa.ISCSI, existingPools) assert.True(t, code.Contains(storage.BackendStateAPIVersionChange), "Should reflect change in ONTAP version") // f: current ONTAP version < cached ONTAP version @@ -6298,7 +6329,7 @@ func TestGetSVMState(t *testing.T) { mockZapiClient.EXPECT().SystemGetOntapiVersion(ctx, false).Return("1.241", nil) mockZapiClient.EXPECT().SystemGetOntapiVersion(ctx, true).Return("1.251", nil) - state, code = getSVMState(ctx, mockONTAPZAPI, protocol, existingPools) + state, code = getSVMState(ctx, mockONTAPZAPI, sa.ISCSI, existingPools) assert.True(t, code.Contains(storage.BackendStateAPIVersionChange), "Should reflect change in ONTAP version") // g: current ONTAP version = cached ONTAP version @@ -6308,7 +6339,7 @@ func TestGetSVMState(t *testing.T) { mockZapiClient.EXPECT().SystemGetOntapiVersion(ctx, false).Return("1.251", nil) mockZapiClient.EXPECT().SystemGetOntapiVersion(ctx, true).Return("1.251", nil) - state, code = getSVMState(ctx, mockONTAPZAPI, protocol, existingPools) + state, code = getSVMState(ctx, mockONTAPZAPI, sa.ISCSI, existingPools) assert.False(t, code.Contains(storage.BackendStateAPIVersionChange), "Should reflect NO change in ONTAP version") } diff --git a/storage_drivers/ontap/ontap_san.go b/storage_drivers/ontap/ontap_san.go index 08e7e1d3c..a852eb526 100644 --- a/storage_drivers/ontap/ontap_san.go +++ b/storage_drivers/ontap/ontap_san.go @@ -35,6 +35,7 @@ type SANStorageDriver struct { initialized bool Config drivers.OntapStorageDriverConfig ips []string + wwpns []string API api.OntapAPI AWSAPI awsapi.AWSAPI telemetry *Telemetry @@ -74,6 +75,8 @@ func (d *SANStorageDriver) BackendName() string { lif0 := "noLIFs" if len(d.ips) > 0 { lif0 = d.ips[0] + } else if len(d.wwpns) > 0 { + lif0 = strings.ReplaceAll(d.wwpns[0], ":", ".") } return CleanBackendName("ontapsan_" + lif0) } else { @@ -128,9 +131,18 @@ func (d *SANStorageDriver) Initialize( } d.Config = *config - if d.Config.SANType == sa.ISCSI { - d.ips, err = d.API.NetInterfaceGetDataLIFs(ctx, "iscsi") - if err != nil { + if d.Config.SANType == sa.FCP { + if d.wwpns, err = d.API.NetFcpInterfaceGetDataLIFs(ctx, d.Config.SANType); err != nil { + return err + } + + if len(d.wwpns) == 0 { + return fmt.Errorf("no FC data LIFs found on SVM %s", d.API.SVMName()) + } else { + Logc(ctx).WithField("dataLIFs", d.wwpns).Debug("Found FC LIFs.") + } + } else { + if d.ips, err = d.API.NetInterfaceGetDataLIFs(ctx, d.Config.SANType); err != nil { return err } @@ -141,8 +153,6 @@ func (d *SANStorageDriver) Initialize( } } - // TODO (vhs): Check if there is a need to validate if FCP interfaces are present. - d.physicalPools, d.virtualPools, err = InitializeStoragePoolsCommon(ctx, d, d.getStoragePoolAttributes(ctx), d.BackendName()) if err != nil { diff --git a/storage_drivers/ontap/ontap_san_test.go b/storage_drivers/ontap/ontap_san_test.go index 31f7d4366..21b66f1cc 100644 --- a/storage_drivers/ontap/ontap_san_test.go +++ b/storage_drivers/ontap/ontap_san_test.go @@ -7,7 +7,6 @@ import ( "encoding/json" "fmt" "net" - "os" "strings" "testing" "time" @@ -3593,7 +3592,6 @@ func TestOntapSANStorageDriverInitialize_WithNameTemplate(t *testing.T) { driver.telemetry.TridentVersion = tridentconfig.OrchestratorVersion.String() driver.telemetry.TridentBackendUUID = BackendUUID - hostname, _ := os.Hostname() message, _ := json.Marshal(driver.GetTelemetry()) mockAPI.EXPECT().IsSVMDRCapable(ctx).Return(true, nil).AnyTimes() @@ -3604,7 +3602,7 @@ func TestOntapSANStorageDriverInitialize_WithNameTemplate(t *testing.T) { mockAPI.EXPECT().IgroupCreate(ctx, gomock.Any(), "iscsi", "linux").Return(nil) mockAPI.EXPECT().IscsiInitiatorGetDefaultAuth(ctx).Return(iscsiInitiatorAuth, nil) mockAPI.EXPECT().NetInterfaceGetDataLIFs(ctx, "iscsi").Return([]string{"1.1.1.1"}, nil) - mockAPI.EXPECT().EmsAutosupportLog(ctx, "ontap-san", "1", false, "heartbeat", hostname, + mockAPI.EXPECT().EmsAutosupportLog(ctx, "ontap-san", "1", false, "heartbeat", "hostname", string(message), 1, "trident", 5).AnyTimes() mockAPI.EXPECT().GetSVMUUID().Return("SVM1-uuid") @@ -3673,7 +3671,6 @@ func TestOntapSANStorageDriverInitialize_NameTemplateDefineInStoragePool(t *test driver.telemetry.TridentVersion = tridentconfig.OrchestratorVersion.String() driver.telemetry.TridentBackendUUID = BackendUUID - hostname, _ := os.Hostname() message, _ := json.Marshal(driver.GetTelemetry()) mockAPI.EXPECT().IsSVMDRCapable(ctx).Return(true, nil).AnyTimes() @@ -3684,7 +3681,7 @@ func TestOntapSANStorageDriverInitialize_NameTemplateDefineInStoragePool(t *test mockAPI.EXPECT().IgroupCreate(ctx, gomock.Any(), "iscsi", "linux").Return(nil) mockAPI.EXPECT().IscsiInitiatorGetDefaultAuth(ctx).Return(iscsiInitiatorAuth, nil) mockAPI.EXPECT().NetInterfaceGetDataLIFs(ctx, "iscsi").Return([]string{"1.1.1.1"}, nil) - mockAPI.EXPECT().EmsAutosupportLog(ctx, "ontap-san", "1", false, "heartbeat", hostname, + mockAPI.EXPECT().EmsAutosupportLog(ctx, "ontap-san", "1", false, "heartbeat", "hostname", string(message), 1, "trident", 5).AnyTimes() mockAPI.EXPECT().GetSVMUUID().Return("SVM1-uuid") @@ -3760,7 +3757,6 @@ func TestOntapSANStorageDriverInitialize_NameTemplateDefineInBothPool(t *testing driver.telemetry.TridentVersion = tridentconfig.OrchestratorVersion.String() driver.telemetry.TridentBackendUUID = BackendUUID - hostname, _ := os.Hostname() message, _ := json.Marshal(driver.GetTelemetry()) mockAPI.EXPECT().IsSVMDRCapable(ctx).Return(true, nil).AnyTimes() @@ -3771,7 +3767,7 @@ func TestOntapSANStorageDriverInitialize_NameTemplateDefineInBothPool(t *testing mockAPI.EXPECT().IgroupCreate(ctx, gomock.Any(), "iscsi", "linux").Return(nil) mockAPI.EXPECT().IscsiInitiatorGetDefaultAuth(ctx).Return(iscsiInitiatorAuth, nil) mockAPI.EXPECT().NetInterfaceGetDataLIFs(ctx, "iscsi").Return([]string{"1.1.1.1"}, nil) - mockAPI.EXPECT().EmsAutosupportLog(ctx, "ontap-san", "1", false, "heartbeat", hostname, + mockAPI.EXPECT().EmsAutosupportLog(ctx, "ontap-san", "1", false, "heartbeat", "hostname", string(message), 1, "trident", 5).AnyTimes() mockAPI.EXPECT().GetSVMUUID().Return("SVM1-uuid") @@ -3887,7 +3883,6 @@ func TestOntapSanStorageDriverInitialize(t *testing.T) { driver.telemetry.TridentVersion = tridentconfig.OrchestratorVersion.String() driver.telemetry.TridentBackendUUID = BackendUUID - hostname, _ := os.Hostname() message, _ := json.Marshal(driver.GetTelemetry()) mockAPI.EXPECT().IsSVMDRCapable(ctx).Return(true, nil).AnyTimes() @@ -3898,7 +3893,56 @@ func TestOntapSanStorageDriverInitialize(t *testing.T) { mockAPI.EXPECT().IgroupCreate(ctx, gomock.Any(), "iscsi", "linux").Return(nil) mockAPI.EXPECT().IscsiInitiatorGetDefaultAuth(ctx).Return(iscsiInitiatorAuth, nil) mockAPI.EXPECT().NetInterfaceGetDataLIFs(ctx, "iscsi").Return([]string{"1.1.1.1"}, nil) - mockAPI.EXPECT().EmsAutosupportLog(ctx, "ontap-san", "1", false, "heartbeat", hostname, + mockAPI.EXPECT().EmsAutosupportLog(ctx, "ontap-san", "1", false, "heartbeat", "hostname", + string(message), 1, "trident", 5).AnyTimes() + mockAPI.EXPECT().GetSVMUUID().Return("SVM1-uuid") + + result := driver.Initialize(ctx, "CSI", configJSON, commonConfig, secrets, BackendUUID) + + assert.NoError(t, result, "Ontap SAN storage driver initialization failed") +} + +func TestOntapSanStorageDriverInitialize_WithFC(t *testing.T) { + ctx := context.Background() + + mockAPI, driver := newMockOntapSANDriver(t) + mockAPI.EXPECT().SVMName().AnyTimes().Return("SVM1") + + commonConfig := getCommonConfig() + + configJSON := ` + { + "version": 1, + "storageDriverName": "ontap-san", + "managementLIF": "127.0.0.1:0", + "svm": "SVM1", + "aggregate": "data", + "username": "dummyuser", + "sanType": "fcp", + "password": "dummypassword" + }` + secrets := map[string]string{ + "clientcertificate": "dummy-certificate", + } + driver.telemetry = &Telemetry{ + Plugin: driver.Name(), + SVM: "SVM1", + Driver: driver, + done: make(chan struct{}), + } + + driver.telemetry.TridentVersion = tridentconfig.OrchestratorVersion.String() + driver.telemetry.TridentBackendUUID = BackendUUID + message, _ := json.Marshal(driver.GetTelemetry()) + + mockAPI.EXPECT().IsSVMDRCapable(ctx).Return(true, nil).AnyTimes() + mockAPI.EXPECT().GetSVMAggregateNames(ctx).AnyTimes().Return([]string{ONTAPTEST_VSERVER_AGGR_NAME}, nil) + mockAPI.EXPECT().GetSVMAggregateAttributes(gomock.Any()).AnyTimes().Return( + map[string]string{ONTAPTEST_VSERVER_AGGR_NAME: "vmdisk"}, nil, + ) + mockAPI.EXPECT().IgroupCreate(ctx, gomock.Any(), "fcp", "linux").Return(nil) + mockAPI.EXPECT().NetFcpInterfaceGetDataLIFs(ctx, "fcp").Return([]string{"10:20:30:40:50:60:70:80"}, nil) + mockAPI.EXPECT().EmsAutosupportLog(ctx, "ontap-san", "1", false, "heartbeat", "hostname", string(message), 1, "trident", 5).AnyTimes() mockAPI.EXPECT().GetSVMUUID().Return("SVM1-uuid") @@ -3964,7 +4008,6 @@ func TestOntapSanStorageDriverInitialize_NetInterfaceGetDataLIFsFail(t *testing. driver.telemetry.TridentVersion = tridentconfig.OrchestratorVersion.String() driver.telemetry.TridentBackendUUID = BackendUUID - hostname, _ := os.Hostname() message, _ := json.Marshal(driver.GetTelemetry()) mockAPI.EXPECT().IsSVMDRCapable(ctx).Return(true, nil).AnyTimes() @@ -3974,7 +4017,54 @@ func TestOntapSanStorageDriverInitialize_NetInterfaceGetDataLIFsFail(t *testing. ) mockAPI.EXPECT().NetInterfaceGetDataLIFs(ctx, "iscsi"). Return(nil, fmt.Errorf("error in getting datalifs")) - mockAPI.EXPECT().EmsAutosupportLog(ctx, "ontap-san", "1", false, "heartbeat", hostname, + mockAPI.EXPECT().EmsAutosupportLog(ctx, "ontap-san", "1", false, "heartbeat", "hostname", + string(message), 1, "trident", 5).AnyTimes() + + result := driver.Initialize(ctx, "CSI", configJSON, commonConfig, secrets, BackendUUID) + + assert.Error(t, result, "Ontap SAN driver initialized") +} + +func TestOntapSanStorageDriverInitialize_FcpInterfaceGetFail(t *testing.T) { + ctx := context.Background() + + mockAPI, driver := newMockOntapSANDriver(t) + mockAPI.EXPECT().SVMName().AnyTimes().Return("SVM1") + + commonConfig := getCommonConfig() + + configJSON := ` + { + "version": 1, + "storageDriverName": "ontap-san", + "managementLIF": "127.0.0.1:0", + "svm": "SVM1", + "aggregate": "data", + "username": "dummyuser", + "sanType": "fcp", + "password": "dummypassword" + }` + secrets := map[string]string{ + "clientcertificate": "dummy-certificate", + } + driver.telemetry = &Telemetry{ + Plugin: driver.Name(), + SVM: "SVM1", + Driver: driver, + done: make(chan struct{}), + } + + driver.telemetry.TridentVersion = tridentconfig.OrchestratorVersion.String() + driver.telemetry.TridentBackendUUID = BackendUUID + message, _ := json.Marshal(driver.GetTelemetry()) + + mockAPI.EXPECT().IsSVMDRCapable(ctx).Return(true, nil).AnyTimes() + mockAPI.EXPECT().GetSVMAggregateNames(ctx).AnyTimes().Return([]string{ONTAPTEST_VSERVER_AGGR_NAME}, nil) + mockAPI.EXPECT().GetSVMAggregateAttributes(gomock.Any()).AnyTimes().Return( + map[string]string{ONTAPTEST_VSERVER_AGGR_NAME: "vmdisk"}, nil, + ) + mockAPI.EXPECT().NetFcpInterfaceGetDataLIFs(ctx, "fcp").Return(nil, fmt.Errorf("error in getting datalifs")) + mockAPI.EXPECT().EmsAutosupportLog(ctx, "ontap-san", "1", false, "heartbeat", "hostname", string(message), 1, "trident", 5).AnyTimes() result := driver.Initialize(ctx, "CSI", configJSON, commonConfig, secrets, BackendUUID)