From b27cfd895c87e0be11510988f51c732b0c42a826 Mon Sep 17 00:00:00 2001 From: Sunnatillo Date: Tue, 15 Sep 2026 17:33:56 +0300 Subject: [PATCH] Add unixGroupID annotation to set UNIX group ownership on ONTAP NAS volumes Add the annotation trident.netapp.io/unixGroupID so users can set the UNIX group ID (GID) of an ONTAP NAS volume at provisioning time. The value is read from the PVC or StorageClass into the volume config, passed through the ONTAP driver's internal volume type, and applied as the NAS group ID on every volume-create call in the REST and ZAPI clients. An empty annotation or a GID of zero means "not set" and leaves the ONTAP default unchanged. The value must be an integer between 1 and 4294967294; anything else returns an error from volume creation. REST uses int64 and ZAPI uses int, so fsgroup_utils.go provides a parser for each. Only the ONTAP driver is affected. A unit test covers both GID parsers, and mocks and existing tests are updated to pass the new argument. Signed-off-by: Sunnatillo --- .../controller_helpers/kubernetes/config.go | 3 + .../controller_helpers/kubernetes/helper.go | 1 + .../mock_ontap/mock_ontap_rest_interface.go | 32 ++++---- .../mock_ontap/mock_ontap_zapi_interface.go | 16 ++-- storage/volume.go | 1 + storage_drivers/ontap/api/abstraction_rest.go | 28 ++++++- .../ontap/api/abstraction_rest_test.go | 20 +++-- storage_drivers/ontap/api/abstraction_zapi.go | 14 +++- .../ontap/api/abstraction_zapi_test.go | 8 +- storage_drivers/ontap/api/fsgroup_utils.go | 59 +++++++++++++++ .../ontap/api/fsgroup_utils_test.go | 75 +++++++++++++++++++ storage_drivers/ontap/api/ontap_rest.go | 32 +++++--- .../ontap/api/ontap_rest_interface.go | 8 +- storage_drivers/ontap/api/ontap_rest_test.go | 32 ++++---- storage_drivers/ontap/api/ontap_zapi.go | 13 +++- .../ontap/api/ontap_zapi_interface.go | 4 +- storage_drivers/ontap/api/ontap_zapi_test.go | 10 +-- storage_drivers/ontap/api/types.go | 1 + storage_drivers/ontap/ontap_nas.go | 1 + storage_drivers/ontap/ontap_nas_flexgroup.go | 1 + 20 files changed, 276 insertions(+), 83 deletions(-) create mode 100644 storage_drivers/ontap/api/fsgroup_utils.go create mode 100644 storage_drivers/ontap/api/fsgroup_utils_test.go diff --git a/frontend/csi/controller_helpers/kubernetes/config.go b/frontend/csi/controller_helpers/kubernetes/config.go index 401cfd68c..4f06a78a4 100644 --- a/frontend/csi/controller_helpers/kubernetes/config.go +++ b/frontend/csi/controller_helpers/kubernetes/config.go @@ -87,6 +87,9 @@ const ( AnnTieringPolicy = prefix + "/tieringPolicy" AnnTieringMinimumCoolingDays = prefix + "/tieringMinimumCoolingDays" + // AnnUnixGroupID sets the UNIX group ownership (GID) of an ONTAP NAS volume at provisioning time. + AnnUnixGroupID = prefix + "/unixGroupID" + // Pod remediation policy annotation and values AnnPodRemediationPolicyAnnotation = prefix + "/podRemediationPolicy" PodRemediationPolicyDelete = "delete" diff --git a/frontend/csi/controller_helpers/kubernetes/helper.go b/frontend/csi/controller_helpers/kubernetes/helper.go index c73d8e951..104759eba 100644 --- a/frontend/csi/controller_helpers/kubernetes/helper.go +++ b/frontend/csi/controller_helpers/kubernetes/helper.go @@ -927,6 +927,7 @@ func getVolumeConfig( UnixPermissions: getAnnotation(annotations, AnnUnixPermissions), StorageClass: storageClass.Name, BlockSize: getAnnotation(annotations, AnnBlockSize), + UnixGroupID: getAnnotation(annotations, AnnUnixGroupID), FileSystem: getAnnotation(annotations, AnnFileSystem), LUKSEncryption: luksEncryption, SplitOnClone: getAnnotation(annotations, AnnSplitOnClone), 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 add432046..dded7dacb 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 @@ -348,31 +348,31 @@ func (mr *MockRestClientInterfaceMockRecorder) FcpNodeGetName(ctx, fields any) * } // FlexGroupCreate mocks base method. -func (m *MockRestClientInterface) FlexGroupCreate(ctx context.Context, name string, size int, aggrs []string, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment string, qosPolicyGroup api.QosPolicyGroup, encrypt *bool, snapshotReserve int) error { +func (m *MockRestClientInterface) FlexGroupCreate(ctx context.Context, name string, size int, aggrs []string, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment string, qosPolicyGroup api.QosPolicyGroup, encrypt *bool, snapshotReserve int, unixGroupID int64) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "FlexGroupCreate", ctx, name, size, aggrs, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, snapshotReserve) + ret := m.ctrl.Call(m, "FlexGroupCreate", ctx, name, size, aggrs, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, snapshotReserve, unixGroupID) ret0, _ := ret[0].(error) return ret0 } // FlexGroupCreate indicates an expected call of FlexGroupCreate. -func (mr *MockRestClientInterfaceMockRecorder) FlexGroupCreate(ctx, name, size, aggrs, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, snapshotReserve any) *gomock.Call { +func (mr *MockRestClientInterfaceMockRecorder) FlexGroupCreate(ctx, name, size, aggrs, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, snapshotReserve, unixGroupID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FlexGroupCreate", reflect.TypeOf((*MockRestClientInterface)(nil).FlexGroupCreate), ctx, name, size, aggrs, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, snapshotReserve) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FlexGroupCreate", reflect.TypeOf((*MockRestClientInterface)(nil).FlexGroupCreate), ctx, name, size, aggrs, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, snapshotReserve, unixGroupID) } // FlexGroupCreateBalanced mocks base method. -func (m *MockRestClientInterface) FlexGroupCreateBalanced(ctx context.Context, name string, size int, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment string, qosPolicyGroup api.QosPolicyGroup, encrypt *bool, snapshotReserve int) error { +func (m *MockRestClientInterface) FlexGroupCreateBalanced(ctx context.Context, name string, size int, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment string, qosPolicyGroup api.QosPolicyGroup, encrypt *bool, snapshotReserve int, unixGroupID int64) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "FlexGroupCreateBalanced", ctx, name, size, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, snapshotReserve) + ret := m.ctrl.Call(m, "FlexGroupCreateBalanced", ctx, name, size, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, snapshotReserve, unixGroupID) ret0, _ := ret[0].(error) return ret0 } // FlexGroupCreateBalanced indicates an expected call of FlexGroupCreateBalanced. -func (mr *MockRestClientInterfaceMockRecorder) FlexGroupCreateBalanced(ctx, name, size, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, snapshotReserve any) *gomock.Call { +func (mr *MockRestClientInterfaceMockRecorder) FlexGroupCreateBalanced(ctx, name, size, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, snapshotReserve, unixGroupID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FlexGroupCreateBalanced", reflect.TypeOf((*MockRestClientInterface)(nil).FlexGroupCreateBalanced), ctx, name, size, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, snapshotReserve) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FlexGroupCreateBalanced", reflect.TypeOf((*MockRestClientInterface)(nil).FlexGroupCreateBalanced), ctx, name, size, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, snapshotReserve, unixGroupID) } // FlexGroupDestroy mocks base method. @@ -2697,33 +2697,33 @@ func (mr *MockRestClientInterfaceMockRecorder) VolumeCloneSplitStart(ctx, volume } // VolumeCreate mocks base method. -func (m *MockRestClientInterface) VolumeCreate(ctx context.Context, name, aggregateName, size, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment string, qosPolicyGroup api.QosPolicyGroup, encrypt *bool, snapshotReserve int, dpVolume bool) (string, error) { +func (m *MockRestClientInterface) VolumeCreate(ctx context.Context, name, aggregateName, size, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment string, qosPolicyGroup api.QosPolicyGroup, encrypt *bool, snapshotReserve int, dpVolume bool, unixGroupID int64) (string, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "VolumeCreate", ctx, name, aggregateName, size, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, snapshotReserve, dpVolume) + ret := m.ctrl.Call(m, "VolumeCreate", ctx, name, aggregateName, size, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, snapshotReserve, dpVolume, unixGroupID) ret0, _ := ret[0].(string) ret1, _ := ret[1].(error) return ret0, ret1 } // VolumeCreate indicates an expected call of VolumeCreate. -func (mr *MockRestClientInterfaceMockRecorder) VolumeCreate(ctx, name, aggregateName, size, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, snapshotReserve, dpVolume any) *gomock.Call { +func (mr *MockRestClientInterfaceMockRecorder) VolumeCreate(ctx, name, aggregateName, size, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, snapshotReserve, dpVolume, unixGroupID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VolumeCreate", reflect.TypeOf((*MockRestClientInterface)(nil).VolumeCreate), ctx, name, aggregateName, size, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, snapshotReserve, dpVolume) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VolumeCreate", reflect.TypeOf((*MockRestClientInterface)(nil).VolumeCreate), ctx, name, aggregateName, size, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, snapshotReserve, dpVolume, unixGroupID) } // VolumeCreateBalanced mocks base method. -func (m *MockRestClientInterface) VolumeCreateBalanced(ctx context.Context, name, size, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment string, qosPolicyGroup api.QosPolicyGroup, encrypt *bool, snapshotReserve int, dpVolume bool) (string, error) { +func (m *MockRestClientInterface) VolumeCreateBalanced(ctx context.Context, name, size, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment string, qosPolicyGroup api.QosPolicyGroup, encrypt *bool, snapshotReserve int, dpVolume bool, unixGroupID int64) (string, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "VolumeCreateBalanced", ctx, name, size, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, snapshotReserve, dpVolume) + ret := m.ctrl.Call(m, "VolumeCreateBalanced", ctx, name, size, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, snapshotReserve, dpVolume, unixGroupID) ret0, _ := ret[0].(string) ret1, _ := ret[1].(error) return ret0, ret1 } // VolumeCreateBalanced indicates an expected call of VolumeCreateBalanced. -func (mr *MockRestClientInterfaceMockRecorder) VolumeCreateBalanced(ctx, name, size, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, snapshotReserve, dpVolume any) *gomock.Call { +func (mr *MockRestClientInterfaceMockRecorder) VolumeCreateBalanced(ctx, name, size, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, snapshotReserve, dpVolume, unixGroupID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VolumeCreateBalanced", reflect.TypeOf((*MockRestClientInterface)(nil).VolumeCreateBalanced), ctx, name, size, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, snapshotReserve, dpVolume) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VolumeCreateBalanced", reflect.TypeOf((*MockRestClientInterface)(nil).VolumeCreateBalanced), ctx, name, size, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, snapshotReserve, dpVolume, unixGroupID) } // VolumeDestroy mocks base method. 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 c9efc8c06..f4fe70448 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 @@ -253,18 +253,18 @@ func (mr *MockZapiClientInterfaceMockRecorder) FcpNodeGetNameRequest() *gomock.C } // FlexGroupCreate mocks base method. -func (m *MockZapiClientInterface) FlexGroupCreate(ctx context.Context, name string, size int, aggrs []azgo.AggrNameType, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment string, qosPolicyGroup api.QosPolicyGroup, encrypt *bool, snapshotReserve int) (*azgo.VolumeCreateAsyncResponse, error) { +func (m *MockZapiClientInterface) FlexGroupCreate(ctx context.Context, name string, size int, aggrs []azgo.AggrNameType, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment string, qosPolicyGroup api.QosPolicyGroup, encrypt *bool, snapshotReserve int, unixGroupID int) (*azgo.VolumeCreateAsyncResponse, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "FlexGroupCreate", ctx, name, size, aggrs, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, snapshotReserve) + ret := m.ctrl.Call(m, "FlexGroupCreate", ctx, name, size, aggrs, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, snapshotReserve, unixGroupID) ret0, _ := ret[0].(*azgo.VolumeCreateAsyncResponse) ret1, _ := ret[1].(error) return ret0, ret1 } // FlexGroupCreate indicates an expected call of FlexGroupCreate. -func (mr *MockZapiClientInterfaceMockRecorder) FlexGroupCreate(ctx, name, size, aggrs, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, snapshotReserve any) *gomock.Call { +func (mr *MockZapiClientInterfaceMockRecorder) FlexGroupCreate(ctx, name, size, aggrs, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, snapshotReserve, unixGroupID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FlexGroupCreate", reflect.TypeOf((*MockZapiClientInterface)(nil).FlexGroupCreate), ctx, name, size, aggrs, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, snapshotReserve) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FlexGroupCreate", reflect.TypeOf((*MockZapiClientInterface)(nil).FlexGroupCreate), ctx, name, size, aggrs, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, snapshotReserve, unixGroupID) } // FlexGroupDestroy mocks base method. @@ -2082,18 +2082,18 @@ func (mr *MockZapiClientInterfaceMockRecorder) VolumeCloneSplitStart(name any) * } // VolumeCreate mocks base method. -func (m *MockZapiClientInterface) VolumeCreate(ctx context.Context, name, aggregateName, size, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment string, qosPolicyGroup api.QosPolicyGroup, encrypt *bool, snapshotReserve int, dpVolume bool) (*azgo.VolumeCreateResponse, error) { +func (m *MockZapiClientInterface) VolumeCreate(ctx context.Context, name, aggregateName, size, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment string, qosPolicyGroup api.QosPolicyGroup, encrypt *bool, snapshotReserve int, dpVolume bool, unixGroupID int) (*azgo.VolumeCreateResponse, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "VolumeCreate", ctx, name, aggregateName, size, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, snapshotReserve, dpVolume) + ret := m.ctrl.Call(m, "VolumeCreate", ctx, name, aggregateName, size, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, snapshotReserve, dpVolume, unixGroupID) ret0, _ := ret[0].(*azgo.VolumeCreateResponse) ret1, _ := ret[1].(error) return ret0, ret1 } // VolumeCreate indicates an expected call of VolumeCreate. -func (mr *MockZapiClientInterfaceMockRecorder) VolumeCreate(ctx, name, aggregateName, size, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, snapshotReserve, dpVolume any) *gomock.Call { +func (mr *MockZapiClientInterfaceMockRecorder) VolumeCreate(ctx, name, aggregateName, size, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, snapshotReserve, dpVolume, unixGroupID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VolumeCreate", reflect.TypeOf((*MockZapiClientInterface)(nil).VolumeCreate), ctx, name, aggregateName, size, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, snapshotReserve, dpVolume) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VolumeCreate", reflect.TypeOf((*MockZapiClientInterface)(nil).VolumeCreate), ctx, name, aggregateName, size, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, snapshotReserve, dpVolume, unixGroupID) } // VolumeDestroy mocks base method. diff --git a/storage/volume.go b/storage/volume.go index c1c67d6aa..4a1cdc014 100644 --- a/storage/volume.go +++ b/storage/volume.go @@ -31,6 +31,7 @@ type VolumeConfig struct { UnixPermissions string `json:"unixPermissions,omitempty"` StorageClass string `json:"storageClass,omitempty"` AccessMode config.AccessMode `json:"accessMode,omitempty"` + UnixGroupID string `json:"unixGroupID,omitempty"` VolumeMode config.VolumeMode `json:"volumeMode,omitempty"` AccessInfo models.VolumeAccessInfo `json:"accessInformation"` // MoveInfo is the operational status of an ongoing volume diff --git a/storage_drivers/ontap/api/abstraction_rest.go b/storage_drivers/ontap/api/abstraction_rest.go index 7a749cd0c..6693ef15c 100644 --- a/storage_drivers/ontap/api/abstraction_rest.go +++ b/storage_drivers/ontap/api/abstraction_rest.go @@ -190,9 +190,14 @@ func (d OntapAPIREST) VolumeCreate(ctx context.Context, volume Volume) (string, aggregateName = volume.Aggregates[0] } + gid, err := parseUnixGroupID(volume.UnixGroupID) + if err != nil { + return "", err + } + volumeUUID, creationErr := d.api.VolumeCreate(ctx, volume.Name, aggregateName, volume.Size, volume.SpaceReserve, volume.SnapshotPolicy, volume.UnixPermissions, volume.ExportPolicy, volume.SecurityStyle, - volume.TieringPolicy, volume.Comment, volume.Qos, volume.Encrypt, volume.SnapshotReserve, volume.DPVolume) + volume.TieringPolicy, volume.Comment, volume.Qos, volume.Encrypt, volume.SnapshotReserve, volume.DPVolume, gid) if creationErr != nil { return "", fmt.Errorf("error creating volume: %v", creationErr) } @@ -216,9 +221,14 @@ func (d OntapAPIREST) VolumeCreateBalanced(ctx context.Context, volume Volume) ( return "", errors.UnsupportedError("ONTAP version does not support balanced placement") } + gid, err := parseUnixGroupID(volume.UnixGroupID) + if err != nil { + return "", err + } + volumeUUID, creationErr := d.api.VolumeCreateBalanced(ctx, volume.Name, volume.Size, volume.SpaceReserve, volume.SnapshotPolicy, volume.UnixPermissions, volume.ExportPolicy, volume.SecurityStyle, - volume.TieringPolicy, volume.Comment, volume.Qos, volume.Encrypt, volume.SnapshotReserve, volume.DPVolume) + volume.TieringPolicy, volume.Comment, volume.Qos, volume.Encrypt, volume.SnapshotReserve, volume.DPVolume, gid) if creationErr != nil { return "", fmt.Errorf("error creating volume: %v", creationErr) } @@ -733,9 +743,14 @@ func (d OntapAPIREST) FlexgroupCreate(ctx context.Context, volume Volume) error return fmt.Errorf("%v is an invalid volume size: %v", volume.Size, err) } + gid, err := parseUnixGroupID(volume.UnixGroupID) + if err != nil { + return err + } + creationErr := d.api.FlexGroupCreate(ctx, volume.Name, volumeSize, volume.Aggregates, volume.SpaceReserve, volume.SnapshotPolicy, volume.UnixPermissions, volume.ExportPolicy, volume.SecurityStyle, volume.TieringPolicy, - volume.Comment, volume.Qos, volume.Encrypt, volume.SnapshotReserve) + volume.Comment, volume.Qos, volume.Encrypt, volume.SnapshotReserve, gid) if creationErr != nil { return fmt.Errorf("error creating volume: %v", creationErr) } @@ -764,9 +779,14 @@ func (d OntapAPIREST) FlexgroupCreateBalanced(ctx context.Context, volume Volume return fmt.Errorf("%v is an invalid volume size: %v", volume.Size, err) } + gid, err := parseUnixGroupID(volume.UnixGroupID) + if err != nil { + return err + } + creationErr := d.api.FlexGroupCreateBalanced(ctx, volume.Name, volumeSize, volume.SpaceReserve, volume.SnapshotPolicy, volume.UnixPermissions, volume.ExportPolicy, volume.SecurityStyle, volume.TieringPolicy, - volume.Comment, volume.Qos, volume.Encrypt, volume.SnapshotReserve) + volume.Comment, volume.Qos, volume.Encrypt, volume.SnapshotReserve, gid) if creationErr != nil { return fmt.Errorf("error creating volume: %v", creationErr) } diff --git a/storage_drivers/ontap/api/abstraction_rest_test.go b/storage_drivers/ontap/api/abstraction_rest_test.go index 2c782dc1e..87987a898 100644 --- a/storage_drivers/ontap/api/abstraction_rest_test.go +++ b/storage_drivers/ontap/api/abstraction_rest_test.go @@ -1570,7 +1570,8 @@ func TestVolumeCreate(t *testing.T) { // case 1: Create volume, returned No error rsi.EXPECT().VolumeCreate(ctx, volume.Name, volume.Aggregates[0], volume.Size, volume.SpaceReserve, volume.SnapshotPolicy, volume.UnixPermissions, volume.ExportPolicy, volume.SecurityStyle, - volume.TieringPolicy, volume.Comment, volume.Qos, volume.Encrypt, volume.SnapshotReserve, volume.DPVolume). + volume.TieringPolicy, volume.Comment, volume.Qos, volume.Encrypt, volume.SnapshotReserve, volume.DPVolume, + int64(0)). Return("", nil) _, err := oapi.VolumeCreate(ctx, volume) assert.NoError(t, err, "error returned while creating volume") @@ -1578,7 +1579,8 @@ func TestVolumeCreate(t *testing.T) { // case 2: Create volume, volume creation failed rsi.EXPECT().VolumeCreate(ctx, volume.Name, volume.Aggregates[0], volume.Size, volume.SpaceReserve, volume.SnapshotPolicy, volume.UnixPermissions, volume.ExportPolicy, volume.SecurityStyle, - volume.TieringPolicy, volume.Comment, volume.Qos, volume.Encrypt, volume.SnapshotReserve, volume.DPVolume). + volume.TieringPolicy, volume.Comment, volume.Qos, volume.Encrypt, volume.SnapshotReserve, volume.DPVolume, + int64(0)). Return("", errors.New("Volume create failed")) _, err = oapi.VolumeCreate(ctx, volume) assert.Error(t, err, "no error returned while creating volume") @@ -1620,7 +1622,8 @@ func TestVolumeCreateBalanced(t *testing.T) { rsi.EXPECT().SupportsFeature(ctx, api.BalancedPlacement).Return(true) rsi.EXPECT().VolumeCreateBalanced(ctx, volume.Name, volume.Size, volume.SpaceReserve, volume.SnapshotPolicy, volume.UnixPermissions, volume.ExportPolicy, volume.SecurityStyle, - volume.TieringPolicy, volume.Comment, volume.Qos, volume.Encrypt, volume.SnapshotReserve, volume.DPVolume). + volume.TieringPolicy, volume.Comment, volume.Qos, volume.Encrypt, volume.SnapshotReserve, volume.DPVolume, + int64(0)). Return("uuid-1", nil) volumeUUID, err := oapi.VolumeCreateBalanced(ctx, volume) assert.NoError(t, err, "error returned while creating volume balanced") @@ -1630,7 +1633,8 @@ func TestVolumeCreateBalanced(t *testing.T) { rsi.EXPECT().SupportsFeature(ctx, api.BalancedPlacement).Return(true) rsi.EXPECT().VolumeCreateBalanced(ctx, volume.Name, volume.Size, volume.SpaceReserve, volume.SnapshotPolicy, volume.UnixPermissions, volume.ExportPolicy, volume.SecurityStyle, - volume.TieringPolicy, volume.Comment, volume.Qos, volume.Encrypt, volume.SnapshotReserve, volume.DPVolume). + volume.TieringPolicy, volume.Comment, volume.Qos, volume.Encrypt, volume.SnapshotReserve, volume.DPVolume, + int64(0)). Return("", errors.New("Volume create balanced failed")) _, err = oapi.VolumeCreateBalanced(ctx, volume) assert.Error(t, err, "no error returned while creating volume balanced") @@ -1818,7 +1822,7 @@ func TestFlexgroupCreate(t *testing.T) { // case 1: Flexgroup create, positive test case rsi.EXPECT().FlexGroupCreate(ctx, volume.Name, 1073741824000, volume.Aggregates, volume.SpaceReserve, volume.SnapshotPolicy, volume.UnixPermissions, volume.ExportPolicy, volume.SecurityStyle, - volume.TieringPolicy, volume.Comment, volume.Qos, volume.Encrypt, volume.SnapshotReserve). + volume.TieringPolicy, volume.Comment, volume.Qos, volume.Encrypt, volume.SnapshotReserve, int64(0)). Return(nil) err := oapi.FlexgroupCreate(ctx, volume) assert.NoError(t, err, "error returned while creating a flexgroup volume") @@ -1826,7 +1830,7 @@ func TestFlexgroupCreate(t *testing.T) { // case 2: Flexgroup create, negative test case rsi.EXPECT().FlexGroupCreate(ctx, volume.Name, 1073741824000, volume.Aggregates, volume.SpaceReserve, volume.SnapshotPolicy, volume.UnixPermissions, volume.ExportPolicy, volume.SecurityStyle, - volume.TieringPolicy, volume.Comment, volume.Qos, volume.Encrypt, volume.SnapshotReserve). + volume.TieringPolicy, volume.Comment, volume.Qos, volume.Encrypt, volume.SnapshotReserve, int64(0)). Return(errors.New("flexgroup volume creation failed")) err = oapi.FlexgroupCreate(ctx, volume) assert.Error(t, err, "no error returned while creating a flexgroup volume") @@ -1866,7 +1870,7 @@ func TestFlexgroupCreateBalanced(t *testing.T) { rsi.EXPECT().SupportsFeature(ctx, api.BalancedPlacement).Return(true) rsi.EXPECT().FlexGroupCreateBalanced(ctx, volume.Name, 1073741824000, volume.SpaceReserve, volume.SnapshotPolicy, volume.UnixPermissions, volume.ExportPolicy, volume.SecurityStyle, - volume.TieringPolicy, volume.Comment, volume.Qos, volume.Encrypt, volume.SnapshotReserve). + volume.TieringPolicy, volume.Comment, volume.Qos, volume.Encrypt, volume.SnapshotReserve, int64(0)). Return(nil) err = oapi.FlexgroupCreateBalanced(ctx, volume) assert.NoError(t, err, "error returned while creating a balanced flexgroup volume") @@ -1875,7 +1879,7 @@ func TestFlexgroupCreateBalanced(t *testing.T) { rsi.EXPECT().SupportsFeature(ctx, api.BalancedPlacement).Return(true) rsi.EXPECT().FlexGroupCreateBalanced(ctx, volume.Name, 1073741824000, volume.SpaceReserve, volume.SnapshotPolicy, volume.UnixPermissions, volume.ExportPolicy, volume.SecurityStyle, - volume.TieringPolicy, volume.Comment, volume.Qos, volume.Encrypt, volume.SnapshotReserve). + volume.TieringPolicy, volume.Comment, volume.Qos, volume.Encrypt, volume.SnapshotReserve, int64(0)). Return(errors.New("flexgroup volume creation failed")) err = oapi.FlexgroupCreateBalanced(ctx, volume) assert.Error(t, err, "no error returned while creating a balanced flexgroup volume") diff --git a/storage_drivers/ontap/api/abstraction_zapi.go b/storage_drivers/ontap/api/abstraction_zapi.go index 8dab40845..461772168 100644 --- a/storage_drivers/ontap/api/abstraction_zapi.go +++ b/storage_drivers/ontap/api/abstraction_zapi.go @@ -75,13 +75,18 @@ func (d OntapAPIZAPI) VolumeCreate(ctx context.Context, volume Volume) (string, defer Logd(ctx, d.driverName, d.api.ClientConfig().DebugTraceFlags["method"]).WithFields(fields).Trace("<<<< VolumeCreate") + gid, err := parseUnixGroupIDInt(volume.UnixGroupID) + if err != nil { + return "", err + } + // ZAPI does not report a volume UUID on create, and the ZAPI path queries WAFL directly rather // than an asynchronous name index, so it is not exposed to the create/delete propagation race. // Callers therefore always delete ZAPI-created volumes by name. volCreateResponse, err := d.api.VolumeCreate(ctx, volume.Name, volume.Aggregates[0], volume.Size, volume.SpaceReserve, volume.SnapshotPolicy, volume.UnixPermissions, volume.ExportPolicy, volume.SecurityStyle, volume.TieringPolicy, volume.Comment, volume.Qos, volume.Encrypt, - volume.SnapshotReserve, volume.DPVolume) + volume.SnapshotReserve, volume.DPVolume, gid) if err != nil { return "", fmt.Errorf("error creating volume: %v", err) } @@ -1363,9 +1368,14 @@ func (d OntapAPIZAPI) FlexgroupCreate(ctx context.Context, volume Volume) error return fmt.Errorf("%v is an invalid volume size: %v", volume.Size, err) } + gid, err := parseUnixGroupIDInt(volume.UnixGroupID) + if err != nil { + return err + } + flexgroupCreateResponse, err := d.api.FlexGroupCreate(ctx, volume.Name, sizeBytes, volume.Aggregates, volume.SpaceReserve, volume.SnapshotPolicy, volume.UnixPermissions, volume.ExportPolicy, volume.SecurityStyle, - volume.TieringPolicy, volume.Comment, volume.Qos, volume.Encrypt, volume.SnapshotReserve) + volume.TieringPolicy, volume.Comment, volume.Qos, volume.Encrypt, volume.SnapshotReserve, gid) if err != nil { return fmt.Errorf("error creating volume: %v", err) } diff --git a/storage_drivers/ontap/api/abstraction_zapi_test.go b/storage_drivers/ontap/api/abstraction_zapi_test.go index 368deb8b1..772765b2d 100644 --- a/storage_drivers/ontap/api/abstraction_zapi_test.go +++ b/storage_drivers/ontap/api/abstraction_zapi_test.go @@ -408,7 +408,7 @@ func TestOntapAPIZAPI_VolumeCreate_Success(t *testing.T) { ctx, volume.Name, volume.Aggregates[0], volume.Size, volume.SpaceReserve, volume.SnapshotPolicy, volume.UnixPermissions, volume.ExportPolicy, volume.SecurityStyle, volume.TieringPolicy, volume.Comment, volume.Qos, - volume.Encrypt, volume.SnapshotReserve, volume.DPVolume, + volume.Encrypt, volume.SnapshotReserve, volume.DPVolume, 0, ).Return(volumeCreateResponse, nil).Times(1) _, err := oapi.VolumeCreate(ctx, volume) @@ -434,7 +434,7 @@ func TestOntapAPIZAPI_VolumeCreate_Error(t *testing.T) { mock.EXPECT().VolumeCreate(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), - gomock.Any()).Return(nil, errors.New("API error")).Times(1) + gomock.Any(), gomock.Any()).Return(nil, errors.New("API error")).Times(1) _, err := oapi.VolumeCreate(ctx, volume) assert.Error(t, err, "expected error when volume creation fails") @@ -467,7 +467,7 @@ func TestOntapAPIZAPI_VolumeCreate_JobExists(t *testing.T) { mock.EXPECT().VolumeCreate(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), - gomock.Any()).Return(volumeCreateResponse, nil).Times(1) + gomock.Any(), gomock.Any()).Return(volumeCreateResponse, nil).Times(1) _, err := oapi.VolumeCreate(ctx, volume) assert.Error(t, err, "expected VolumeCreateJobExistsError when volume create job already exists") @@ -2907,7 +2907,7 @@ func TestOntapAPIZAPI_FlexgroupCreate(t *testing.T) { mock.EXPECT().FlexGroupCreate(ctx, tt.volume.Name, sizeBytes, tt.volume.Aggregates, tt.volume.SpaceReserve, tt.volume.SnapshotPolicy, tt.volume.UnixPermissions, tt.volume.ExportPolicy, tt.volume.SecurityStyle, tt.volume.TieringPolicy, - tt.volume.Comment, tt.volume.Qos, tt.volume.Encrypt, tt.volume.SnapshotReserve). + tt.volume.Comment, tt.volume.Qos, tt.volume.Encrypt, tt.volume.SnapshotReserve, 0). Return(tt.mockResponse, tt.mockError).Times(1) } diff --git a/storage_drivers/ontap/api/fsgroup_utils.go b/storage_drivers/ontap/api/fsgroup_utils.go new file mode 100644 index 000000000..58c11f9ce --- /dev/null +++ b/storage_drivers/ontap/api/fsgroup_utils.go @@ -0,0 +1,59 @@ +// Copyright 2025 NetApp, Inc. All Rights Reserved. + +package api + +import ( + "fmt" + "strconv" +) + +const ( + // minUnixGroupID and maxUnixGroupID define the inclusive range of valid UNIX group IDs (GIDs) + // that may be applied to an ONTAP NAS volume. + minUnixGroupID = 1 + maxUnixGroupID = 4294967294 +) + +// parseUnixGroupID parses a UNIX group ID (GID) string for the REST path, returning an int64. +// An empty string is treated as "not specified" and returns 0 with no error, meaning no group ID +// is set on the volume. Any non-numeric value, or a value outside the inclusive range +// 1-4294967294, returns an error. +func parseUnixGroupID(unixGroupID string) (int64, error) { + if unixGroupID == "" { + return 0, nil + } + + gid, err := strconv.ParseInt(unixGroupID, 10, 64) + if err != nil { + return 0, fmt.Errorf("invalid UNIX group ID %q: %w", unixGroupID, err) + } + + if gid < minUnixGroupID || gid > maxUnixGroupID { + return 0, fmt.Errorf("UNIX group ID %d is out of range (must be between %d and %d)", + gid, minUnixGroupID, maxUnixGroupID) + } + + return gid, nil +} + +// parseUnixGroupIDInt parses a UNIX group ID (GID) string for the ZAPI path, returning an int. +// An empty string is treated as "not specified" and returns 0 with no error, meaning no group ID +// is set on the volume. Any non-numeric value, or a value outside the inclusive range +// 1-4294967294, returns an error. +func parseUnixGroupIDInt(unixGroupID string) (int, error) { + if unixGroupID == "" { + return 0, nil + } + + gid, err := strconv.Atoi(unixGroupID) + if err != nil { + return 0, fmt.Errorf("invalid UNIX group ID %q: %w", unixGroupID, err) + } + + if gid < minUnixGroupID || gid > maxUnixGroupID { + return 0, fmt.Errorf("UNIX group ID %d is out of range (must be between %d and %d)", + gid, minUnixGroupID, maxUnixGroupID) + } + + return gid, nil +} diff --git a/storage_drivers/ontap/api/fsgroup_utils_test.go b/storage_drivers/ontap/api/fsgroup_utils_test.go new file mode 100644 index 000000000..93c8d13e7 --- /dev/null +++ b/storage_drivers/ontap/api/fsgroup_utils_test.go @@ -0,0 +1,75 @@ +// Copyright 2025 NetApp, Inc. All Rights Reserved. + +package api + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestParseUnixGroupID(t *testing.T) { + tests := []struct { + name string + input string + expected int64 + expectError bool + }{ + {name: "empty string treated as not set", input: "", expected: 0, expectError: false}, + {name: "minimum valid GID", input: "1", expected: 1, expectError: false}, + {name: "typical GID", input: "1000", expected: 1000, expectError: false}, + {name: "maximum valid GID", input: "4294967294", expected: 4294967294, expectError: false}, + {name: "zero is out of range", input: "0", expected: 0, expectError: true}, + {name: "above maximum is out of range", input: "4294967295", expected: 0, expectError: true}, + {name: "negative is out of range", input: "-1", expected: 0, expectError: true}, + {name: "non-numeric returns error", input: "abc", expected: 0, expectError: true}, + {name: "whitespace returns error", input: " 100 ", expected: 0, expectError: true}, + {name: "float returns error", input: "100.5", expected: 0, expectError: true}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + gid, err := parseUnixGroupID(test.input) + if test.expectError { + assert.Error(t, err, "expected an error for input %q", test.input) + assert.Equal(t, int64(0), gid, "expected zero GID on error") + } else { + assert.NoError(t, err, "unexpected error for input %q", test.input) + assert.Equal(t, test.expected, gid, "unexpected GID for input %q", test.input) + } + }) + } +} + +func TestParseUnixGroupIDInt(t *testing.T) { + tests := []struct { + name string + input string + expected int + expectError bool + }{ + {name: "empty string treated as not set", input: "", expected: 0, expectError: false}, + {name: "minimum valid GID", input: "1", expected: 1, expectError: false}, + {name: "typical GID", input: "1000", expected: 1000, expectError: false}, + {name: "maximum valid GID", input: "4294967294", expected: 4294967294, expectError: false}, + {name: "zero is out of range", input: "0", expected: 0, expectError: true}, + {name: "above maximum is out of range", input: "4294967295", expected: 0, expectError: true}, + {name: "negative is out of range", input: "-1", expected: 0, expectError: true}, + {name: "non-numeric returns error", input: "abc", expected: 0, expectError: true}, + {name: "whitespace returns error", input: " 100 ", expected: 0, expectError: true}, + {name: "float returns error", input: "100.5", expected: 0, expectError: true}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + gid, err := parseUnixGroupIDInt(test.input) + if test.expectError { + assert.Error(t, err, "expected an error for input %q", test.input) + assert.Equal(t, 0, gid, "expected zero GID on error") + } else { + assert.NoError(t, err, "unexpected error for input %q", test.input) + assert.Equal(t, test.expected, gid, "unexpected GID for input %q", test.input) + } + }) + } +} diff --git a/storage_drivers/ontap/api/ontap_rest.go b/storage_drivers/ontap/api/ontap_rest.go index 28f1b86b5..d8871c937 100644 --- a/storage_drivers/ontap/api/ontap_rest.go +++ b/storage_drivers/ontap/api/ontap_rest.go @@ -1580,7 +1580,7 @@ func (c *RestClient) listAllVolumeNamesBackedBySnapshot(ctx context.Context, vol func (c *RestClient) createVolumeByStyle( ctx context.Context, name string, sizeInBytes int64, aggrs []string, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment string, - qosPolicyGroup QosPolicyGroup, encrypt *bool, snapshotReserve int, style string, dpVolume bool, + qosPolicyGroup QosPolicyGroup, encrypt *bool, snapshotReserve int, style string, dpVolume bool, unixGroupID int64, ) (string, error) { params := storage.NewVolumeCreateParamsWithTimeout(c.httpClient.Timeout) params.Context = ctx @@ -1657,6 +1657,10 @@ func (c *RestClient) createVolumeByStyle( volumeNas.ExportPolicy = &models.VolumeInlineNasInlineExportPolicy{Name: &exportPolicy} volumeInfo.Nas = volumeNas } + if unixGroupID != 0 { + volumeNas.Gid = &unixGroupID + volumeInfo.Nas = volumeNas + } params.SetInfo(volumeInfo) @@ -1706,7 +1710,7 @@ func (c *RestClient) waitForVolumeVisible(ctx context.Context, name, style strin func (c *RestClient) createApplicationContainerByStyleWithUUID( ctx context.Context, name string, sizeInBytes int64, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment string, qosPolicyGroup QosPolicyGroup, encrypt *bool, - snapshotReserve int, dpVolume bool, style string, + snapshotReserve int, dpVolume bool, style string, unixGroupID int64, ) (string, error) { params := application.NewContainerCreateParamsWithTimeout(c.httpClient.Timeout) params.Context = ctx @@ -1769,6 +1773,10 @@ func (c *RestClient) createApplicationContainerByStyleWithUUID( nas.UnixPermissions = &volumePermissions haveNAS = true } + if unixGroupID != 0 { + nas.Gid = &unixGroupID + haveNAS = true + } if haveNAS { volumeInfo.Nas = nas @@ -1835,11 +1843,11 @@ func (c *RestClient) createApplicationContainerByStyleWithUUID( func (c *RestClient) createApplicationContainerByStyle( ctx context.Context, name string, sizeInBytes int64, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment string, qosPolicyGroup QosPolicyGroup, encrypt *bool, - snapshotReserve int, dpVolume bool, style string, + snapshotReserve int, dpVolume bool, style string, unixGroupID int64, ) error { _, err := c.createApplicationContainerByStyleWithUUID(ctx, name, sizeInBytes, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, - snapshotReserve, dpVolume, style) + snapshotReserve, dpVolume, style, unixGroupID) return err } @@ -2010,7 +2018,7 @@ func (c *RestClient) VolumeListByAttrs( func (c *RestClient) VolumeCreate( ctx context.Context, name, aggregateName, size, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment string, - qosPolicyGroup QosPolicyGroup, encrypt *bool, snapshotReserve int, dpVolume bool, + qosPolicyGroup QosPolicyGroup, encrypt *bool, snapshotReserve int, dpVolume bool, unixGroupID int64, ) (string, error) { sizeBytesStr, err := capacity.ToBytes(size) if err != nil { @@ -2023,13 +2031,13 @@ func (c *RestClient) VolumeCreate( return c.createVolumeByStyle(ctx, name, sizeInBytes, []string{aggregateName}, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, snapshotReserve, - models.VolumeStyleFlexvol, dpVolume) + models.VolumeStyleFlexvol, dpVolume, unixGroupID) } func (c *RestClient) VolumeCreateBalanced( ctx context.Context, name, size, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment string, qosPolicyGroup QosPolicyGroup, encrypt *bool, - snapshotReserve int, dpVolume bool, + snapshotReserve int, dpVolume bool, unixGroupID int64, ) (string, error) { sizeBytesStr, err := capacity.ToBytes(size) if err != nil { @@ -2042,7 +2050,7 @@ func (c *RestClient) VolumeCreateBalanced( return c.createApplicationContainerByStyleWithUUID(ctx, name, sizeInBytes, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, - snapshotReserve, dpVolume, models.VolumeStyleFlexvol) + snapshotReserve, dpVolume, models.VolumeStyleFlexvol, unixGroupID) } // VolumeModify modifies a few select Flexvol attributes @@ -5288,22 +5296,22 @@ func ToSliceVolumeAggregatesItems(aggrs []string) []*models.VolumeInlineAggregat func (c *RestClient) FlexGroupCreate( ctx context.Context, name string, size int, aggrs []string, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment string, qosPolicyGroup QosPolicyGroup, encrypt *bool, - snapshotReserve int, + snapshotReserve int, unixGroupID int64, ) error { _, err := c.createVolumeByStyle(ctx, name, int64(size), aggrs, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, snapshotReserve, - models.VolumeStyleFlexgroup, false) + models.VolumeStyleFlexgroup, false, unixGroupID) return err } func (c *RestClient) FlexGroupCreateBalanced( ctx context.Context, name string, size int, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment string, qosPolicyGroup QosPolicyGroup, encrypt *bool, - snapshotReserve int, + snapshotReserve int, unixGroupID int64, ) error { return c.createApplicationContainerByStyle(ctx, name, int64(size), spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment, qosPolicyGroup, encrypt, - snapshotReserve, false /*dpVolume*/, models.VolumeStyleFlexgroup) + snapshotReserve, false /*dpVolume*/, models.VolumeStyleFlexgroup, unixGroupID) } // FlexgroupModify modifies a few select Flexgroup attributes diff --git a/storage_drivers/ontap/api/ontap_rest_interface.go b/storage_drivers/ontap/api/ontap_rest_interface.go index 7ffa0949b..99346dcf3 100644 --- a/storage_drivers/ontap/api/ontap_rest_interface.go +++ b/storage_drivers/ontap/api/ontap_rest_interface.go @@ -51,12 +51,12 @@ type RestClientInterface interface { VolumeCreate( ctx context.Context, name, aggregateName, size, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment string, qosPolicyGroup QosPolicyGroup, encrypt *bool, - snapshotReserve int, dpVolume bool, + snapshotReserve int, dpVolume bool, unixGroupID int64, ) (string, error) VolumeCreateBalanced( ctx context.Context, name, size, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment string, qosPolicyGroup QosPolicyGroup, encrypt *bool, - snapshotReserve int, dpVolume bool, + snapshotReserve int, dpVolume bool, unixGroupID int64, ) (string, error) // VolumeModify modifies one or more volume attributes VolumeModify(ctx context.Context, volume Volume) error @@ -280,12 +280,12 @@ type RestClientInterface interface { FlexGroupCreate( ctx context.Context, name string, size int, aggrs []string, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment string, qosPolicyGroup QosPolicyGroup, encrypt *bool, - snapshotReserve int, + snapshotReserve int, unixGroupID int64, ) error FlexGroupCreateBalanced( ctx context.Context, name string, size int, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment string, qosPolicyGroup QosPolicyGroup, encrypt *bool, - snapshotReserve int, + snapshotReserve int, unixGroupID int64, ) error // FlexgroupModify modifies one or more volume attributes FlexgroupModify(ctx context.Context, volume Volume) error diff --git a/storage_drivers/ontap/api/ontap_rest_test.go b/storage_drivers/ontap/api/ontap_rest_test.go index ca84e97c6..fc7aaed88 100644 --- a/storage_drivers/ontap/api/ontap_rest_test.go +++ b/storage_drivers/ontap/api/ontap_rest_test.go @@ -5082,7 +5082,7 @@ func TestOntapRestCreateVolumeByStyleInvalidUnixPermission(t *testing.T) { _, err := rs.createVolumeByStyle(ctx, "fakeVolume", 1073741824, []string{"aggr1"}, "spaceReserve", "fakeSnapshotPolicy", "invalidUnixPermission", "fake-exportpolicy", "unix", "fake-tier", "comment", QosPolicyGroup{Name: "qosPolicy", Kind: QosPolicyGroupKind}, new(true), 0, models.VolumeStyleFlexvol, - false) + false, int64(0)) assert.Error(t, err, "volume created") server.Close() } @@ -5105,7 +5105,7 @@ func TestOntapREST_VolumeCreate(t *testing.T) { _, err := rs.VolumeCreate(ctx, "fakeVolume", "aggr1", "1g", "spaceReserve", "fakeSnapshotPolicy", "---rwxr-xr-x", "fake-exportpolicy", "unix", "fake-tier", - "comment", QosPolicyGroup{Name: "qosPolicy", Kind: QosPolicyGroupKind}, &encrypt, 0, false) + "comment", QosPolicyGroup{Name: "qosPolicy", Kind: QosPolicyGroupKind}, &encrypt, 0, false, int64(0)) if !test.isErrorExpected { assert.NoError(t, err, "could not create a volume") } else { @@ -5151,7 +5151,7 @@ func TestOntapREST_VolumeCreateBalanced(t *testing.T) { volumeUUID, err := rs.VolumeCreateBalanced(ctx, "fakeVolume", "1g", "spaceReserve", "fakeSnapshotPolicy", "---rwxr-xr-x", "fake-exportpolicy", "unix", "fake-tier", - "comment", QosPolicyGroup{Name: "qosPolicy", Kind: QosPolicyGroupKind}, &encrypt, 0, false) + "comment", QosPolicyGroup{Name: "qosPolicy", Kind: QosPolicyGroupKind}, &encrypt, 0, false, int64(0)) if !test.isErrorExpected { assert.NoError(t, err, "could not create a balanced volume") assert.NotEmpty(t, volumeUUID, "balanced volume create should return the UUID from its visibility GET") @@ -5173,7 +5173,7 @@ func TestCreateApplicationContainerByStyle_Success(t *testing.T) { err := rs.createApplicationContainerByStyle(ctx, "fakeVolume", 1073741824, "none", "fakeSnapshotPolicy", "---rwxr-xr-x", "fake-exportpolicy", "unix", "fake-tier", "comment", QosPolicyGroup{Name: "qosPolicy", Kind: QosPolicyGroupKind}, &encrypt, 10, false, - models.VolumeStyleFlexvol) + models.VolumeStyleFlexvol, int64(0)) assert.NoError(t, err, "expected container create to succeed") } @@ -5187,7 +5187,7 @@ func TestCreateApplicationContainerByStyle_FlexgroupStyle(t *testing.T) { err := rs.createApplicationContainerByStyle(ctx, "fakeVolume", 1073741824, "none", "fakeSnapshotPolicy", "---rwxr-xr-x", "fake-exportpolicy", "unix", "fake-tier", "comment", QosPolicyGroup{Name: "qosPolicy", Kind: QosPolicyGroupKind}, &encrypt, 10, false, - models.VolumeStyleFlexgroup) + models.VolumeStyleFlexgroup, int64(0)) assert.NoError(t, err, "expected container create with flexgroup style to succeed") } @@ -5201,7 +5201,7 @@ func TestCreateApplicationContainerByStyle_DPVolume(t *testing.T) { err := rs.createApplicationContainerByStyle(ctx, "fakeVolume", 1073741824, "none", "fakeSnapshotPolicy", "", "", "", "", "", QosPolicyGroup{Kind: InvalidQosPolicyGroupKind}, &encrypt, NumericalValueNotSet, true, - models.VolumeStyleFlexvol) + models.VolumeStyleFlexvol, int64(0)) assert.NoError(t, err, "expected container create for DP volume to succeed") } @@ -5214,7 +5214,7 @@ func TestCreateApplicationContainerByStyle_InvalidUnixPermissions(t *testing.T) err := rs.createApplicationContainerByStyle(ctx, "fakeVolume", 1073741824, "none", "fakeSnapshotPolicy", "invalidUnixPermission", "fake-exportpolicy", "unix", "", "", QosPolicyGroup{Kind: InvalidQosPolicyGroupKind}, nil, NumericalValueNotSet, false, - models.VolumeStyleFlexvol) + models.VolumeStyleFlexvol, int64(0)) assert.Error(t, err, "expected error for invalid unix permissions") assert.Contains(t, err.Error(), "cannot process unix permissions") } @@ -5228,7 +5228,7 @@ func TestCreateApplicationContainerByStyle_EncryptNil(t *testing.T) { err := rs.createApplicationContainerByStyle(ctx, "fakeVolume", 1073741824, "none", "fakeSnapshotPolicy", "", "", "", "", "", QosPolicyGroup{Kind: InvalidQosPolicyGroupKind}, nil, NumericalValueNotSet, false, - models.VolumeStyleFlexvol) + models.VolumeStyleFlexvol, int64(0)) assert.NoError(t, err, "expected container create with nil encrypt to succeed") } @@ -5241,7 +5241,7 @@ func TestCreateApplicationContainerByStyle_NoNASOptions(t *testing.T) { err := rs.createApplicationContainerByStyle(ctx, "fakeVolume", 1073741824, "none", "", "", "", "", "", "", QosPolicyGroup{Kind: InvalidQosPolicyGroupKind}, nil, NumericalValueNotSet, false, - models.VolumeStyleFlexvol) + models.VolumeStyleFlexvol, int64(0)) assert.NoError(t, err, "expected container create with no NAS options to succeed") } @@ -5254,7 +5254,7 @@ func TestCreateApplicationContainerByStyle_WithTieringPolicy(t *testing.T) { err := rs.createApplicationContainerByStyle(ctx, "fakeVolume", 1073741824, "none", "", "", "", "", "auto", "", QosPolicyGroup{Kind: InvalidQosPolicyGroupKind}, nil, NumericalValueNotSet, false, - models.VolumeStyleFlexvol) + models.VolumeStyleFlexvol, int64(0)) assert.NoError(t, err, "expected container create with tiering policy to succeed") } @@ -5267,7 +5267,7 @@ func TestCreateApplicationContainerByStyle_BackendError(t *testing.T) { err := rs.createApplicationContainerByStyle(ctx, "fakeVolume", 1073741824, "none", "fakeSnapshotPolicy", "", "", "", "", "", QosPolicyGroup{Kind: InvalidQosPolicyGroupKind}, nil, NumericalValueNotSet, false, - models.VolumeStyleFlexvol) + models.VolumeStyleFlexvol, int64(0)) assert.Error(t, err, "expected error from backend") } @@ -5284,7 +5284,7 @@ func TestCreateApplicationContainerByStyle_NilAcceptedResponse(t *testing.T) { err := rs.createApplicationContainerByStyle(ctx, "fakeVolume", 1073741824, "none", "", "", "", "", "", "", QosPolicyGroup{Kind: InvalidQosPolicyGroupKind}, nil, NumericalValueNotSet, false, - models.VolumeStyleFlexvol) + models.VolumeStyleFlexvol, int64(0)) assert.Error(t, err, "expected error for nil accepted response") } @@ -5297,7 +5297,7 @@ func TestCreateApplicationContainerByStyle_WithQoSPolicy(t *testing.T) { err := rs.createApplicationContainerByStyle(ctx, "fakeVolume", 1073741824, "none", "", "---rwxr-xr-x", "default", "unix", "", "test comment", QosPolicyGroup{Name: "myQos", Kind: QosPolicyGroupKind}, nil, 5, false, - models.VolumeStyleFlexvol) + models.VolumeStyleFlexvol, int64(0)) assert.NoError(t, err, "expected container create with QoS policy to succeed") } @@ -5310,7 +5310,7 @@ func TestCreateApplicationContainerByStyle_WithSnapshotReserve(t *testing.T) { err := rs.createApplicationContainerByStyle(ctx, "fakeVolume", 1073741824, "none", "daily", "", "", "", "", "", QosPolicyGroup{Kind: InvalidQosPolicyGroupKind}, nil, 20, false, - models.VolumeStyleFlexvol) + models.VolumeStyleFlexvol, int64(0)) assert.NoError(t, err, "expected container create with snapshot reserve to succeed") } @@ -5739,7 +5739,7 @@ func TestOntapREST_FlexGroupCreate(t *testing.T) { err := rs.FlexGroupCreate(ctx, "fakeVolume", 1073741824, []string{"aggr1"}, "spaceReserve", "fakeSnapshotPolicy", "---rwxr-xr-x", "fake-exportpolicy", "unix", "fake-tier", - "comment", QosPolicyGroup{Name: "qosPolicy", Kind: QosPolicyGroupKind}, new(true), 0) + "comment", QosPolicyGroup{Name: "qosPolicy", Kind: QosPolicyGroupKind}, new(true), 0, int64(0)) if !test.isErrorExpected { assert.NoError(t, err, "could not create a flexgroup volume") } else { @@ -5768,7 +5768,7 @@ func TestOntapREST_FlexGroupCreateBalanced(t *testing.T) { err := rs.FlexGroupCreateBalanced(ctx, "fakeVolume", 1073741824, "spaceReserve", "fakeSnapshotPolicy", "---rwxr-xr-x", "fake-exportpolicy", "unix", "fake-tier", - "comment", QosPolicyGroup{Name: "qosPolicy", Kind: QosPolicyGroupKind}, &encrypt, 0) + "comment", QosPolicyGroup{Name: "qosPolicy", Kind: QosPolicyGroupKind}, &encrypt, 0, int64(0)) if !test.isErrorExpected { assert.NoError(t, err, "could not create a balanced flexgroup volume") } else { diff --git a/storage_drivers/ontap/api/ontap_zapi.go b/storage_drivers/ontap/api/ontap_zapi.go index 7d0874f84..687134374 100644 --- a/storage_drivers/ontap/api/ontap_zapi.go +++ b/storage_drivers/ontap/api/ontap_zapi.go @@ -777,7 +777,7 @@ func (c Client) LunSize(lunPath string) (int, error) { func (c Client) FlexGroupCreate( ctx context.Context, name string, size int, aggrs []azgo.AggrNameType, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment string, qosPolicyGroup QosPolicyGroup, - encrypt *bool, snapshotReserve int, + encrypt *bool, snapshotReserve int, unixGroupID int, ) (*azgo.VolumeCreateAsyncResponse, error) { junctionPath := fmt.Sprintf("/%s", name) @@ -804,6 +804,10 @@ func (c Client) FlexGroupCreate( if unixPermissions != "" { request.SetUnixPermissions(unixPermissions) } + // Set the UNIX group ownership (GID) only when specified (non-zero). + if unixGroupID != 0 { + request.SetGroupId(unixGroupID) + } // For encrypt == nil - we don't explicitely set the encrypt argument. // If destination aggregate is NAE enabled, new volume will be aggregate encrypted // else it will be volume encrypted as per Ontap's default behaviour. @@ -1179,7 +1183,7 @@ func (c Client) JobGetIterStatus(jobId int) (*azgo.JobGetIterResponse, error) { func (c Client) VolumeCreate( ctx context.Context, name, aggregateName, size, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment string, qosPolicyGroup QosPolicyGroup, encrypt *bool, - snapshotReserve int, dpVolume bool, + snapshotReserve int, dpVolume bool, unixGroupID int, ) (*azgo.VolumeCreateResponse, error) { request := azgo.NewVolumeCreateRequest(). SetVolume(name). @@ -1208,6 +1212,11 @@ func (c Client) VolumeCreate( request.SetUnixPermissions(unixPermissions) } + // Set the UNIX group ownership (GID) only when specified (non-zero). + if unixGroupID != 0 { + request.SetGroupId(unixGroupID) + } + // Allowed ONTAP tiering Policy values // // ================================================================================= diff --git a/storage_drivers/ontap/api/ontap_zapi_interface.go b/storage_drivers/ontap/api/ontap_zapi_interface.go index 0a7a768c6..0f2546b60 100644 --- a/storage_drivers/ontap/api/ontap_zapi_interface.go +++ b/storage_drivers/ontap/api/ontap_zapi_interface.go @@ -112,7 +112,7 @@ type ZapiClientInterface interface { LunSize(lunPath string) (int, error) // FlexGroupCreate creates a FlexGroup with the specified options // equivalent to filer::> volume create -vserver svm_name -volume fg_vol_name –auto-provision-as flexgroup -size fg_size -state online -type RW -policy default -unix-permissions ---rwxr-xr-x -space-guarantee none -snapshot-policy none -security-style unix -encrypt false - FlexGroupCreate(ctx context.Context, name string, size int, aggrs []azgo.AggrNameType, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment string, qosPolicyGroup QosPolicyGroup, encrypt *bool, snapshotReserve int) (*azgo.VolumeCreateAsyncResponse, error) + FlexGroupCreate(ctx context.Context, name string, size int, aggrs []azgo.AggrNameType, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment string, qosPolicyGroup QosPolicyGroup, encrypt *bool, snapshotReserve int, unixGroupID int) (*azgo.VolumeCreateAsyncResponse, error) // FlexGroupDestroy destroys a FlexGroup FlexGroupDestroy(ctx context.Context, name string, force bool) (*azgo.VolumeDestroyAsyncResponse, error) // FlexGroupExists tests for the existence of a FlexGroup @@ -138,7 +138,7 @@ type ZapiClientInterface interface { JobGetIterStatus(jobId int) (*azgo.JobGetIterResponse, error) // VolumeCreate creates a volume with the specified options // equivalent to filer::> volume create -vserver iscsi_vs -volume v -aggregate aggr1 -size 1g -state online -type RW -policy default -unix-permissions ---rwxr-xr-x -space-guarantee none -snapshot-policy none -security-style unix -encrypt false - VolumeCreate(ctx context.Context, name, aggregateName, size, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment string, qosPolicyGroup QosPolicyGroup, encrypt *bool, snapshotReserve int, dpVolume bool) (*azgo.VolumeCreateResponse, error) + VolumeCreate(ctx context.Context, name, aggregateName, size, spaceReserve, snapshotPolicy, unixPermissions, exportPolicy, securityStyle, tieringPolicy, comment string, qosPolicyGroup QosPolicyGroup, encrypt *bool, snapshotReserve int, dpVolume bool, unixGroupID int) (*azgo.VolumeCreateResponse, error) VolumeModifyExportPolicy(volumeName, exportPolicyName string) (*azgo.VolumeModifyIterResponse, error) VolumeModifyUnixPermissions(volumeName, unixPermissions string) (*azgo.VolumeModifyIterResponse, error) // VolumeCloneCreate clones a volume from a snapshot diff --git a/storage_drivers/ontap/api/ontap_zapi_test.go b/storage_drivers/ontap/api/ontap_zapi_test.go index 368c28f58..e3d577728 100644 --- a/storage_drivers/ontap/api/ontap_zapi_test.go +++ b/storage_drivers/ontap/api/ontap_zapi_test.go @@ -1353,13 +1353,13 @@ func TestZAPI_Priority1_CoreInfrastructure(t *testing.T) { // Test with all parameters volResult, err := client.VolumeCreate(ctx, "test-vol", "aggr1", "1g", "none", "default", "755", "default", "unix", "none", "test comment", - qosPolicy, new(true), 20, false) + qosPolicy, new(true), 20, false, 0) assert.NoError(t, err, "expected no error creating volume with fake server") assert.NotNil(t, volResult, "volume result should not be nil when creation succeeds") // Test with DP volume (different code path) dpResult, err := client.VolumeCreate(ctx, "dp-vol", "aggr1", "1g", "none", - "default", "", "default", "unix", "none", "", qosPolicy, nil, -1, true) + "default", "", "default", "unix", "none", "", qosPolicy, nil, -1, true, 0) assert.NoError(t, err, "expected no error creating DP volume with fake server") assert.NotNil(t, dpResult, "DP volume result should not be nil when creation succeeds") @@ -1367,7 +1367,7 @@ func TestZAPI_Priority1_CoreInfrastructure(t *testing.T) { adaptiveQos, err := NewQosPolicyGroup("", "adaptive-policy") assert.NoError(t, err, "expected no error creating adaptive QoS policy") adaptiveResult, err := client.VolumeCreate(ctx, "test-vol2", "aggr1", "1g", "none", - "default", "755", "default", "unix", "none", "", adaptiveQos, nil, -1, false) + "default", "755", "default", "unix", "none", "", adaptiveQos, nil, -1, false, 0) assert.NoError(t, err, "expected no error creating volume with adaptive QoS") assert.NotNil(t, adaptiveResult, "adaptive volume result should not be nil when creation succeeds") }) @@ -1408,13 +1408,13 @@ func TestZAPI_Priority1_FlexGroupOperations(t *testing.T) { // Test with all parameters (fixed signature) _, err := client.FlexGroupCreate(ctx, "test-flexgroup", 2, aggrList, "none", - "default", "755", "default", "unix", "none", "test comment", qosPolicy, new(true), 20) + "default", "755", "default", "unix", "none", "test comment", qosPolicy, new(true), 20, 0) assert.NoError(t, err) // Fake server supports this operation // Test with adaptive QoS (fixed signature) adaptiveQos, _ := NewQosPolicyGroup("", "adaptive-policy") _, err = client.FlexGroupCreate(ctx, "test-flexgroup2", 4, aggrList, "none", - "default", "755", "default", "unix", "backup", "", adaptiveQos, nil, -1) + "default", "755", "default", "unix", "backup", "", adaptiveQos, nil, -1, 0) assert.NoError(t, err) // Fake server supports this operation }) diff --git a/storage_drivers/ontap/api/types.go b/storage_drivers/ontap/api/types.go index bfaa25516..2ddba33a5 100644 --- a/storage_drivers/ontap/api/types.go +++ b/storage_drivers/ontap/api/types.go @@ -51,6 +51,7 @@ type Volume struct { UnixPermissions string UUID string DPVolume bool + UnixGroupID string } type ( diff --git a/storage_drivers/ontap/ontap_nas.go b/storage_drivers/ontap/ontap_nas.go index aa9aa039f..5f09c7bf5 100644 --- a/storage_drivers/ontap/ontap_nas.go +++ b/storage_drivers/ontap/ontap_nas.go @@ -439,6 +439,7 @@ func (d *NASStorageDriver) Create( SnapshotReserve: snapshotReserveInt, TieringPolicy: tieringPolicy, UnixPermissions: unixPermissions, + UnixGroupID: volConfig.UnixGroupID, DPVolume: volConfig.IsMirrorDestination, } diff --git a/storage_drivers/ontap/ontap_nas_flexgroup.go b/storage_drivers/ontap/ontap_nas_flexgroup.go index 610cce71f..71deef3b0 100644 --- a/storage_drivers/ontap/ontap_nas_flexgroup.go +++ b/storage_drivers/ontap/ontap_nas_flexgroup.go @@ -451,6 +451,7 @@ func (d *NASFlexGroupStorageDriver) Create( SnapshotReserve: snapshotReserveInt, TieringPolicy: tieringPolicy, UnixPermissions: unixPermissions, + UnixGroupID: volConfig.UnixGroupID, DPVolume: volConfig.IsMirrorDestination, }, useBalancedPlacement) if err != nil {