Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions frontend/common/volumes.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,17 @@ func GetVolumeConfig(
snapshotDir = snapDirFormatted
}

// If preserveUnlink is provided, ensure it is lower case
preserveUnlink := collection.GetV(opts, "preserveUnlink", "")
if preserveUnlink != "" {
preserveUnlinkFormatted, err := convert.ToFormattedBool(preserveUnlink)
if err != nil {
Logc(ctx).WithError(err).Errorf("Invalid boolean value for preserveUnlink: %v.", preserveUnlink)
return nil, err
}
preserveUnlink = preserveUnlinkFormatted
}

cfg := &storage.VolumeConfig{
Name: name,
Size: fmt.Sprintf("%d", sizeBytes),
Expand All @@ -150,6 +161,7 @@ func GetVolumeConfig(
SplitOnClone: collection.GetV(opts, "splitOnClone", ""),
SnapshotPolicy: collection.GetV(opts, "snapshotPolicy", ""),
SnapshotReserve: collection.GetV(opts, "snapshotReserve", ""),
PreserveUnlink: preserveUnlink,
SnapshotDir: snapshotDir,
ExportPolicy: collection.GetV(opts, "exportPolicy", ""),
UnixPermissions: collection.GetV(opts, "unixPermissions", ""),
Expand Down
1 change: 1 addition & 0 deletions frontend/csi/controller_helpers/kubernetes/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ const (
AnnSnapshotPolicy = prefix + "/snapshotPolicy"
AnnSnapshotReserve = prefix + "/snapshotReserve"
AnnSnapshotDir = prefix + "/snapshotDirectory"
AnnPreserveUnlink = prefix + "/preserveUnlink"
AnnUnixPermissions = prefix + "/unixPermissions"
AnnExportPolicy = prefix + "/exportPolicy"
AnnBlockSize = prefix + "/blockSize"
Expand Down
11 changes: 11 additions & 0 deletions frontend/csi/controller_helpers/kubernetes/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -841,6 +841,16 @@ func getVolumeConfig(
snapshotDirAnn = snapDirFormatted
}

// If preserveUnlink annotation is provided, ensure it is lower case
preserveUnlinkAnn := getAnnotation(annotations, AnnPreserveUnlink)
if preserveUnlinkAnn != "" {
preserveUnlinkFormatted, err := convert.ToFormattedBool(preserveUnlinkAnn)
if err != nil {
Logc(ctx).WithError(err).Errorf("Invalid boolean value for preserveUnlink annotation: %v.", preserveUnlinkAnn)
}
preserveUnlinkAnn = preserveUnlinkFormatted
}

if getAnnotation(annotations, AnnReadOnlyClone) == "" {
annotations[AnnReadOnlyClone] = "false"
}
Expand Down Expand Up @@ -886,6 +896,7 @@ func getVolumeConfig(
SnapshotPolicy: getAnnotation(annotations, AnnSnapshotPolicy),
SnapshotReserve: getAnnotation(annotations, AnnSnapshotReserve),
SnapshotDir: snapshotDirAnn,
PreserveUnlink: preserveUnlinkAnn,
ExportPolicy: getAnnotation(annotations, AnnExportPolicy),
UnixPermissions: getAnnotation(annotations, AnnUnixPermissions),
StorageClass: storageClass.Name,
Expand Down
1 change: 1 addition & 0 deletions storage/volume.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type VolumeConfig struct {
SecurityStyle string `json:"securityStyle"`
SnapshotPolicy string `json:"snapshotPolicy,omitempty"`
SnapshotReserve string `json:"snapshotReserve,omitempty"`
PreserveUnlink string `json:"preserveUnlink,omitempty"`
SnapshotDir string `json:"snapshotDirectory,omitempty"`
ExportPolicy string `json:"exportPolicy,omitempty"`
UnixPermissions string `json:"unixPermissions,omitempty"`
Expand Down
1 change: 1 addition & 0 deletions storage_drivers/ontap/api/abstraction.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ type OntapAPI interface {
) (string, error)
VolumeRecoveryQueuePurge(ctx context.Context, recoveryQueueVolumeName string) error
VolumeRecoveryQueueGetName(ctx context.Context, name string) (string, error)
PreserveUnlinkSet(ctx context.Context, volumeName string) error
SMBShareCreate(ctx context.Context, shareName, path string) error
SMBShareExists(ctx context.Context, shareName string) (bool, error)
SMBShareDestroy(ctx context.Context, shareName string) error
Expand Down
8 changes: 8 additions & 0 deletions storage_drivers/ontap/api/abstraction_rest.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,14 @@ func (d OntapAPIREST) VolumeRecoveryQueueGetName(ctx context.Context, name strin
return recoveryQueueVolumeName, nil
}

func (d OntapAPIREST) PreserveUnlinkSet(ctx context.Context, volumeName string) error {
preserveUnlinkErr := d.api.PreserveUnlinkSet(ctx, volumeName)
if preserveUnlinkErr != nil {
return fmt.Errorf("error setting preserveUnlink %v: %v", volumeName, preserveUnlinkErr)
}
return nil
}

func (d OntapAPIREST) VolumeInfo(ctx context.Context, name string) (*Volume, error) {
fields := []string{
"type", "size", "comment", "aggregates", "nas", "guarantee",
Expand Down
6 changes: 6 additions & 0 deletions storage_drivers/ontap/api/abstraction_zapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,12 @@ func (d OntapAPIZAPI) VolumeDestroy(ctx context.Context, name string, force, ski
return nil
}

// The -is-preserve-unlink-enabled flag is only supported with newer ONTAP versions that use REST.
func (d OntapAPIZAPI) PreserveUnlinkSet(ctx context.Context, volumeName string) error {
Logc(ctx).WithField("volume", volumeName).Warn("preserveUnlink cannot be set with ZAPI.")
return nil
}

func (d OntapAPIZAPI) VolumeRecoveryQueuePurge(ctx context.Context, recoveryQueueVolumeName string) error {
volRecoveryQueuePurgeResponse, err := d.api.VolumeRecoveryQueuePurge(recoveryQueueVolumeName)
if err != nil {
Expand Down
61 changes: 61 additions & 0 deletions storage_drivers/ontap/api/ontap_rest.go
Original file line number Diff line number Diff line change
Expand Up @@ -1770,6 +1770,67 @@ func (c *RestClient) VolumeRecoveryQueueGetName(ctx context.Context, name string
return responseObject.Records[0].Volume, nil
}

// PreserveUnlinkSet uses the cli passthrough REST API to set the is-preserve-unlink-enabled option on a volume.
// This volume option was introduced in 9.12.1 but not made visible via the /storage/volumes REST API endpoint.
func (c *RestClient) PreserveUnlinkSet(ctx context.Context, volumeName string) error {
fields := LogFields{
"Method": "PreserveUnlinkSet",
"Type": "ontap_rest",
"volumeName": volumeName,
"vserver": c.svmName,
}
Logd(ctx, c.driverName, c.config.DebugTraceFlags["method"]).WithFields(fields).
Trace(">>>> PreserveUnlinkSet")
defer Logd(ctx, c.driverName, c.config.DebugTraceFlags["method"]).WithFields(fields).
Trace("<<<< PrserveUnlinkSet")

requestUrl := fmt.Sprintf("https://%s/api/private/cli/volume?vserver=%s&volume=%s",
c.config.ManagementLIF, c.svmName, volumeName)

requestContent := map[string]string{
"is-preserve-unlink-enabled": "true",
}
requestBodyBytes, err := json.Marshal(requestContent)
if err != nil {
return err
}

request, err := http.NewRequestWithContext(ctx, http.MethodPatch, requestUrl, bytes.NewReader(requestBodyBytes))
if err != nil {
return err
}

response, err := c.sendPassThroughCliCommand(ctx, request)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, @arndt-netapp. Trident aggressively avoids using the CLI passthrough because we need to address SVMs by UUID instead of by name. Is there another way we can accomplish this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, the is-preserve-unlink-enabled volume parameter is not exposed via the standard ONTAP REST API, which is why I implemented this with the private CLI passthrough.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood, and we need to lobby for that, or we need the CLI passthough to accept SVM UUIDs. Every other REST API accepts SVM UUIDs, so it seems reasonable for the CLI to do so as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I filed CONTAP-595771 to request REST API support for the is-preserve-unlink-enabled volume parameter. If we can deliver this now via the CLI passthrough, and move to using the standard /storage/volumes endpoint down the road, that would be ideal given how long this customer request has been waiting already!

if err != nil {
return err
}

defer func() { _ = response.Body.Close() }()

var responseBodyBytes []byte
var responseBodyString string
if response.Body != nil {
responseBodyBytes, _ = io.ReadAll(response.Body)
responseBodyString = string(responseBodyBytes)
} else {
Logc(ctx).WithField("statusCode", response.StatusCode).Error(
"no response when trying to set preserveUnlink.")
return fmt.Errorf("no response with status code: %v", response.StatusCode)
}
if response.StatusCode != http.StatusOK {
Logc(ctx).WithField("statusCode", response.StatusCode).Error(
"failed to set preserveUnlink: %s", responseBodyString)
return fmt.Errorf("unexpected response status code: %v", response.StatusCode)
}
if !strings.Contains(responseBodyString, "modify successful") {
Logc(ctx).WithField("statusCode", response.StatusCode).Error(
"unexpected response when setting preserveUnlink: %s", responseBodyString)
return fmt.Errorf("unexpected response boday with status code: %v", response.StatusCode)
}

return nil
}

func (c *RestClient) sendPassThroughCliCommand(ctx context.Context, request *http.Request) (*http.Response, error) {
request.Header.Set("Content-Type", "application/json")

Expand Down
2 changes: 2 additions & 0 deletions storage_drivers/ontap/api/ontap_rest_interface.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions storage_drivers/ontap/api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type Volume struct {
SnapshotDir *bool
SnapshotPolicy string
SnapshotReserve int
PreserveUnlink *bool
SnapshotSpaceUsed int
SpaceReserve string
TieringPolicy string
Expand Down
47 changes: 47 additions & 0 deletions storage_drivers/ontap/ontap_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ const (
SpaceReserve = "spaceReserve"
SnapshotPolicy = "snapshotPolicy"
SnapshotReserve = "snapshotReserve"
PreserveUnlink = "preserveUnlink"
UnixPermissions = "unixPermissions"
ExportPolicy = "exportPolicy"
SecurityStyle = "securityStyle"
Expand Down Expand Up @@ -1748,6 +1749,7 @@ const (
DefaultSpaceReserve = "none"
DefaultSnapshotPolicy = "none"
DefaultSnapshotReserve = "5"
DefaultPreserveUnlink = "false"
DefaultUnixPermissions = "---rwxrwxrwx"
DefaultSnapshotDir = "false"
DefaultExportPolicy = "default"
Expand Down Expand Up @@ -1812,6 +1814,10 @@ func PopulateConfigurationDefaults(ctx context.Context, config *drivers.OntapSto
}
}

if config.PreserveUnlink == "" {
config.PreserveUnlink = DefaultPreserveUnlink
}

// If snapshotDir is provided, ensure it is lower case
snapDir := DefaultSnapshotDir
if config.SnapshotDir != "" {
Expand All @@ -1822,6 +1828,16 @@ func PopulateConfigurationDefaults(ctx context.Context, config *drivers.OntapSto
}
config.SnapshotDir = snapDir

// If preserveUnlink is provided, ensure it is lower case
preserveUnlink := DefaultPreserveUnlink
if config.PreserveUnlink != "" {
if preserveUnlink, err = convert.ToFormattedBool(config.PreserveUnlink); err != nil {
Logc(ctx).WithError(err).Errorf("Invalid boolean value for preserveUnlink: %v.", config.PreserveUnlink)
return fmt.Errorf("invalid boolean value for preserveUnlink: %v", err)
}
}
config.PreserveUnlink = preserveUnlink

if config.DenyNewVolumePools == "" {
config.DenyNewVolumePools = DefaultDenyNewVolumePools
} else {
Expand Down Expand Up @@ -1950,6 +1966,7 @@ func PopulateConfigurationDefaults(ctx context.Context, config *drivers.OntapSto
"SpaceReserve": config.SpaceReserve,
"SnapshotPolicy": config.SnapshotPolicy,
"SnapshotReserve": config.SnapshotReserve,
"PreserveUnlink": config.PreserveUnlink,
"UnixPermissions": config.UnixPermissions,
"SnapshotDir": config.SnapshotDir,
"ExportPolicy": config.ExportPolicy,
Expand Down Expand Up @@ -2615,6 +2632,10 @@ func getVolumeExternalCommon(
if volume.SnapshotDir != nil {
snapshotDir = *volume.SnapshotDir
}
preserveUnlink := false
if volume.PreserveUnlink != nil {
preserveUnlink = *volume.PreserveUnlink
}
volumeConfig := &storage.VolumeConfig{
Version: tridentconfig.OrchestratorAPIVersion,
Name: name,
Expand All @@ -2625,6 +2646,7 @@ func getVolumeExternalCommon(
SnapshotReserve: strconv.Itoa(volume.SnapshotReserve),
ExportPolicy: volume.ExportPolicy,
SnapshotDir: strconv.FormatBool(snapshotDir),
PreserveUnlink: strconv.FormatBool(preserveUnlink),
UnixPermissions: volume.UnixPermissions,
StorageClass: "",
AccessMode: tridentconfig.ReadWriteMany,
Expand Down Expand Up @@ -2901,6 +2923,7 @@ func setStoragePoolAttributes(
pool.InternalAttributes()[SpaceReserve] = config.SpaceReserve
pool.InternalAttributes()[SnapshotPolicy] = config.SnapshotPolicy
pool.InternalAttributes()[SnapshotReserve] = config.SnapshotReserve
pool.InternalAttributes()[PreserveUnlink] = config.PreserveUnlink
pool.InternalAttributes()[SplitOnClone] = config.SplitOnClone
pool.InternalAttributes()[Encryption] = config.Encryption
pool.InternalAttributes()[LUKSEncryption] = config.LUKSEncryption
Expand Down Expand Up @@ -3023,6 +3046,11 @@ func initializeVirtualPools(
snapshotReserve = vpool.SnapshotReserve
}

preserveUnlink := config.PreserveUnlink
if vpool.PreserveUnlink != "" {
preserveUnlink = vpool.PreserveUnlink
}

splitOnClone := config.SplitOnClone
if vpool.SplitOnClone != "" {
splitOnClone = vpool.SplitOnClone
Expand Down Expand Up @@ -3155,6 +3183,7 @@ func initializeVirtualPools(
pool.InternalAttributes()[SpaceReserve] = spaceReserve
pool.InternalAttributes()[SnapshotPolicy] = snapshotPolicy
pool.InternalAttributes()[SnapshotReserve] = snapshotReserve
pool.InternalAttributes()[PreserveUnlink] = preserveUnlink
pool.InternalAttributes()[SplitOnClone] = splitOnClone
pool.InternalAttributes()[UnixPermissions] = unixPermissions
pool.InternalAttributes()[SnapshotDir] = snapshotDir
Expand Down Expand Up @@ -3696,6 +3725,16 @@ func getVolumeOptsCommon(
opts["snapshotDir"] = snapshotDirFormatted
}

// If preserveUnlink is provided, ensure it is lower case
if volConfig.PreserveUnlink != "" {
preserveUnlinkFormatted, err := convert.ToFormattedBool(volConfig.PreserveUnlink)
if err != nil {
Logc(ctx).WithError(err).Errorf(
"Invalid boolean value for volume '%v' preserveUnlink: %v.", volConfig.Name, volConfig.PreserveUnlink)
}
opts["preserveUnlink"] = preserveUnlinkFormatted
}

// If skipRecoveryQueue is provided, ensure it is lower case
if volConfig.SkipRecoveryQueue != "" {
skipRecoveryQueueFormatted, err := convert.ToFormattedBool(volConfig.SkipRecoveryQueue)
Expand Down Expand Up @@ -5474,6 +5513,14 @@ func purgeRecoveryQueueVolume(ctx context.Context, api api.OntapAPI, volumeName
}
}

// setPreserveUnlink has to be handled specially
func setPresrveUnlink(ctx context.Context, api api.OntapAPI, volumeName string) {
if err := api.PreserveUnlinkSet(ctx, volumeName); err != nil {
Logc(ctx).WithField("volume",
volumeName).Errorf("error setting preserveUnlink: %v", err)
}
}

// getSMBShareNamePath constructs the SMB share name and path based on the provided parameters.
func getSMBShareNamePath(flexvol, name string, secureSMBEnabled bool) (string, string) {
if secureSMBEnabled {
Expand Down
15 changes: 14 additions & 1 deletion storage_drivers/ontap/ontap_nas.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ func (d *NASStorageDriver) Create(
spaceReserve = collection.GetV(opts, "spaceReserve", storagePool.InternalAttributes()[SpaceReserve])
snapshotPolicy = collection.GetV(opts, "snapshotPolicy", storagePool.InternalAttributes()[SnapshotPolicy])
snapshotReserve = collection.GetV(opts, "snapshotReserve", storagePool.InternalAttributes()[SnapshotReserve])
preserveUnlink = collection.GetV(opts, "preserveUnlink", storagePool.InternalAttributes()[PreserveUnlink])
unixPermissions = collection.GetV(opts, "unixPermissions", storagePool.InternalAttributes()[UnixPermissions])
snapshotDir = collection.GetV(opts, "snapshotDir", storagePool.InternalAttributes()[SnapshotDir])
exportPolicy = collection.GetV(opts, "exportPolicy", storagePool.InternalAttributes()[ExportPolicy])
Expand All @@ -280,7 +281,7 @@ func (d *NASStorageDriver) Create(
qosPolicy = storagePool.InternalAttributes()[QosPolicy]
adaptiveQosPolicy = storagePool.InternalAttributes()[AdaptiveQosPolicy]
)

snapshotReserveInt, err := GetSnapshotReserve(snapshotPolicy, snapshotReserve)
if err != nil {
return fmt.Errorf("invalid value for snapshotReserve: %v", err)
Expand Down Expand Up @@ -313,6 +314,11 @@ func (d *NASStorageDriver) Create(
return fmt.Errorf("invalid boolean value for snapshotDir: %v", err)
}

enablePreserveUnlink, err := strconv.ParseBool(preserveUnlink)
if err != nil {
return fmt.Errorf("invalid boolean value for preserveUnlink: %v", err)
}

enableEncryption, configEncryption, err := GetEncryptionValue(encryption)
if err != nil {
return fmt.Errorf("invalid boolean value for encryption: %v", err)
Expand Down Expand Up @@ -355,6 +361,7 @@ func (d *NASStorageDriver) Create(
volConfig.SpaceReserve = spaceReserve
volConfig.SnapshotPolicy = snapshotPolicy
volConfig.SnapshotReserve = snapshotReserve
volConfig.PreserveUnlink = preserveUnlink
volConfig.UnixPermissions = unixPermissions
volConfig.SnapshotDir = snapshotDir
volConfig.ExportPolicy = exportPolicy
Expand All @@ -370,6 +377,7 @@ func (d *NASStorageDriver) Create(
"spaceReserve": spaceReserve,
"snapshotPolicy": snapshotPolicy,
"snapshotReserve": snapshotReserveInt,
"preserveUnlink": enablePreserveUnlink,
"unixPermissions": unixPermissions,
"snapshotDir": enableSnapshotDir,
"exportPolicy": exportPolicy,
Expand Down Expand Up @@ -463,6 +471,11 @@ func (d *NASStorageDriver) Create(
}
}

// Set is-unlink-preserve-enabled if required
if enablePreserveUnlink {
setPresrveUnlink(ctx, d.API, volConfig.InternalName)
}

return nil
}

Expand Down
1 change: 1 addition & 0 deletions storage_drivers/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ type OntapStorageDriverConfigDefaults struct {
SpaceReserve string `json:"spaceReserve"`
SnapshotPolicy string `json:"snapshotPolicy"`
SnapshotReserve string `json:"snapshotReserve"`
PreserveUnlink string `json:"preserveUnlink"`
SnapshotDir string `json:"snapshotDir"`
UnixPermissions string `json:"unixPermissions"`
ExportPolicy string `json:"exportPolicy"`
Expand Down
Loading