Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Enhancement]: Add SVM storage_limit parameter #328

Open
wants to merge 10 commits into
base: integration/main
Choose a base branch
from
4 changes: 3 additions & 1 deletion docs/data-sources/svm_data_source.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Retrieves the configuration of SVM
## Supported Platforms

* On-prem ONTAP system 9.6 or higher
* `storage_limit` attribute supported with ONTAP system 9.13 or higher
Copy link
Contributor

Choose a reason for hiding this comment

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

This needs to be added in the list of attributes on line 37-46

* Amazon FSx for NetApp ONTAP

## Example Usage
Expand Down Expand Up @@ -42,4 +43,5 @@ data "netapp-ontap_svm" "svm" {
- `language` (String) Language to use for svm
- `max_volumes` (String) Maximum number of volumes that can be created on the svm. Expects an integer or unlimited
- `snapshot_policy` (String) The name of the snapshot policy to manage
- `subtype` (String) The subtype for svm to be created
- `subtype` (String) The subtype for svm to be created
- `storage_limit` (Number) Maximum storage permitted on svm, in bytes
2 changes: 2 additions & 0 deletions docs/data-sources/svms_data_source.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Retrieves the configuration of SVMs.
## Supported Platforms

* On-prem ONTAP system 9.6 or higher
* `storage_limit` attribute supported with ONTAP system 9.13 or higher
Copy link
Contributor

Choose a reason for hiding this comment

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

This needs to be added in the list of attributes on line 64-72

* Amazon FSx for NetApp ONTAP

## Example Usage
Expand Down Expand Up @@ -69,3 +70,4 @@ Read-Only:
- `max_volumes` (String) Maximum number of volumes that can be created on the svm. Expects an integer or unlimited
- `snapshot_policy` (String) The name of the snapshot policy to manage
- `subtype` (String) The subtype for svm to be created
- `storage_limit` (Number) Maximum storage permitted on svm, in bytes
9 changes: 7 additions & 2 deletions docs/resources/svm_resource.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ Create/Modify/Delete a SVM
## Supported Platforms

* On-prem ONTAP system 9.6 or higher
* `storage_limit` attribute supported with ONTAP system 9.13 or higher
Copy link
Contributor

Choose a reason for hiding this comment

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

This needs to be added in the list of attributes on line 47-63

* Amazon FSx for NetApp ONTAP

## Example Usage

This creates a new SVM called `tfsvm4`. In IPspace `terraformIpspace_newname`, which can have up to 200 volumes which will be cased on aggr2
This creates a new SVM called `tfsvm4` in IPspace `terraformIpspace_newname`, which can have up to 200 volumes and up to 1 GB storage, which will be cased on aggr2.

```terraform
resource "netapp-ontap_svm" "example" {
Expand All @@ -35,8 +36,11 @@ resource "netapp-ontap_svm" "example" {
comment = "test"
snapshot_policy = "default-1weekly"
language = "en_us.utf_8"
aggregates = ["aggr2"]
aggregates = [
{ name = "aggr2" }
]
max_volumes = "200"
storage_limit = 1073741824
}`
```

Expand All @@ -57,6 +61,7 @@ resource "netapp-ontap_svm" "example" {
- `max_volumes` (String) Maximum number of volumes that can be created on the svm. Expects an integer or unlimited
- `snapshot_policy` (String) The name of the snapshot policy to manage
- `subtype` (String) The subtype for svm to be created
- `storage_limit` (Number) Maximum storage permitted on svm, in bytes

### Read-Only

Expand Down
14 changes: 9 additions & 5 deletions examples/resources/netapp-ontap_svm/resource.tf
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
resource "netapp-ontap_svm" "example" {
cx_profile_name = "cluster2"
name = "tfsvm"
ipspace = "test"
comment = "test"
name = "tfsvm"
ipspace = "test"
comment = "test"
snapshot_policy = "default-1weekly"
//subtype = "dp_destination"
language = "en_us.utf_8"
aggregates = ["aggr1", "test"]
max_volumes = "200"
aggregates = [
{ name = "aggr1" },
{ name = "test" },
]
max_volumes = "200"
storage_limit = 1073741824
}
73 changes: 57 additions & 16 deletions internal/interfaces/svm.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,27 +24,29 @@ type SvmDataModelONTAP struct {

// SvmResourceModel describes the resource data model.
type SvmResourceModel struct {
Name string `mapstructure:"name,omitempty"`
Ipspace Ipspace `mapstructure:"ipspace"`
SnapshotPolicy SnapshotPolicy `mapstructure:"snapshot_policy,omitempty"`
SubType string `mapstructure:"subtype,omitempty"`
Aggregates []map[string]string `mapstructure:"aggregates"`
Comment string `mapstructure:"comment"`
Ipspace Ipspace `mapstructure:"ipspace"`
Language string `mapstructure:"language,omitempty"`
MaxVolumes string `mapstructure:"max_volumes,omitempty"`
Aggregates []map[string]string `mapstructure:"aggregates"`
Name string `mapstructure:"name,omitempty"`
SnapshotPolicy SnapshotPolicy `mapstructure:"snapshot_policy,omitempty"`
Storage Storage `mapstructure:"storage,omitempty"`
SubType string `mapstructure:"subtype,omitempty"`
}

// SvmGetDataSourceModel describes the data source model.
type SvmGetDataSourceModel struct {
Name string `mapstructure:"name"`
UUID string `mapstructure:"uuid"`
Ipspace Ipspace `mapstructure:"ipspace"`
SnapshotPolicy SnapshotPolicy `mapstructure:"snapshot_policy"`
SubType string `mapstructure:"subtype,omitempty"`
Aggregates []Aggregate `mapstructure:"aggregates,omitempty"`
Comment string `mapstructure:"comment,omitempty"`
Ipspace Ipspace `mapstructure:"ipspace"`
Language string `mapstructure:"language,omitempty"`
Aggregates []Aggregate `mapstructure:"aggregates,omitempty"`
MaxVolumes string `mapstructure:"max_volumes,omitempty"`
Name string `mapstructure:"name"`
SnapshotPolicy SnapshotPolicy `mapstructure:"snapshot_policy"`
Storage Storage `mapstructure:"storage,omitempty"`
SubType string `mapstructure:"subtype,omitempty"`
UUID string `mapstructure:"uuid"`
}

// Ipspace describes the resource data model.
Expand All @@ -57,6 +59,11 @@ type SnapshotPolicy struct {
Name string `mapstructure:"name,omitempty"`
}

// Storage describes the resource data model.
type Storage struct {
Limit int `mapstructure:"limit,omitempty"`
}

// SvmDataSourceFilterModel describes the data source data model for queries.
type SvmDataSourceFilterModel struct {
Name string `mapstructure:"name"`
Expand Down Expand Up @@ -122,11 +129,25 @@ func GetSvmByNameIgnoreNotFound(errorHandler *utils.ErrorHandler, r restclient.R
}

// GetSvmByNameDataSource to get data source svm info
func GetSvmByNameDataSource(errorHandler *utils.ErrorHandler, r restclient.RestClient, name string) (*SvmGetDataSourceModel, error) {
func GetSvmByNameDataSource(errorHandler *utils.ErrorHandler, r restclient.RestClient, name string, version versionModelONTAP) (*SvmGetDataSourceModel, error) {
api := "svm/svms"
query := r.NewQuery()
query.Fields([]string{"name", "ipspace", "snapshot_policy", "subtype", "comment", "language", "max_volumes", "aggregates"})
fields := []string{
"name",
"ipspace",
"snapshot_policy",
"subtype",
"comment",
"language",
"max_volumes",
"aggregates",
}
if version.Generation >= 9 && version.Major >= 13 {
fields = append(fields, "storage.limit")
}
query.Fields(fields)
query.Add("name", name)

statusCode, response, err := r.GetNilOrOneRecord(api, query, nil)
if err == nil && response == nil {
err = fmt.Errorf("no response for GET %s", api)
Expand All @@ -144,10 +165,23 @@ func GetSvmByNameDataSource(errorHandler *utils.ErrorHandler, r restclient.RestC
}

// GetSvmsByName to get data source list svm info
func GetSvmsByName(errorHandler *utils.ErrorHandler, r restclient.RestClient, filter *SvmDataSourceFilterModel) ([]SvmGetDataSourceModel, error) {
func GetSvmsByName(errorHandler *utils.ErrorHandler, r restclient.RestClient, filter *SvmDataSourceFilterModel, version versionModelONTAP) ([]SvmGetDataSourceModel, error) {
api := "svm/svms"
query := r.NewQuery()
query.Fields([]string{"name", "ipspace", "snapshot_policy", "subtype", "comment", "language", "max_volumes", "aggregates"})
fields := []string{
"name",
"ipspace",
"snapshot_policy",
"subtype",
"comment",
"language",
"max_volumes",
"aggregates",
}
if version.Generation >= 9 && version.Major >= 13 {
fields = append(fields, "storage.limit")
}
query.Fields(fields)

if filter != nil {
var filterMap map[string]interface{}
Expand Down Expand Up @@ -179,7 +213,7 @@ func GetSvmsByName(errorHandler *utils.ErrorHandler, r restclient.RestClient, fi
}

// CreateSvm to create svm
func CreateSvm(errorHandler *utils.ErrorHandler, r restclient.RestClient, data SvmResourceModel, setAggrEmpty bool, setCommentEmpty bool) (*SvmGetDataModelONTAP, error) {
func CreateSvm(errorHandler *utils.ErrorHandler, r restclient.RestClient, data SvmResourceModel, setAggrEmpty bool, setCommentEmpty bool, setStorageLimitEmpty bool) (*SvmGetDataModelONTAP, error) {
var body map[string]interface{}
if err := mapstructure.Decode(data, &body); err != nil {
return nil, errorHandler.MakeAndReportError("error encoding svm body", fmt.Sprintf("error on encoding svm/svms body: %s, body: %#v", err, data))
Expand All @@ -190,6 +224,13 @@ func CreateSvm(errorHandler *utils.ErrorHandler, r restclient.RestClient, data S
if setCommentEmpty {
delete(body, "comment")
}
if setStorageLimitEmpty {
// delete storage.limit from request body, so that ONTAP uses default value
if v, ok := body["storage"].(map[string]interface{}); ok {
delete(v, "limit")
}
}

query := r.NewQuery()
query.Add("return_records", "true")
statusCode, response, err := r.CallCreateMethod("svm/svms", query, body)
Expand Down
18 changes: 17 additions & 1 deletion internal/provider/svm/svm_data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ type SvmDataSourceModel struct {
Language types.String `tfsdk:"language"`
Aggregates []types.String `tfsdk:"aggregates"`
MaxVolumes types.String `tfsdk:"max_volumes"`
StorageLimit types.Int64 `tfsdk:"storage_limit"`
ID types.String `tfsdk:"id"`
}

Expand Down Expand Up @@ -108,6 +109,10 @@ func (d *SvmDataSource) Schema(ctx context.Context, req datasource.SchemaRequest
MarkdownDescription: "Maximum number of volumes that can be created on the svm. Expects an integer or unlimited",
Computed: true,
},
"storage_limit": schema.Int64Attribute{
MarkdownDescription: "Maximum storage permitted on svm, in bytes",
Computed: true,
},
"id": schema.StringAttribute{
Computed: true,
},
Expand Down Expand Up @@ -150,7 +155,17 @@ func (d *SvmDataSource) Read(ctx context.Context, req datasource.ReadRequest, re
return
}

restInfo, err := interfaces.GetSvmByNameDataSource(errorHandler, *client, data.Name.ValueString())
cluster, err := interfaces.GetCluster(errorHandler, *client)
if err != nil {
// error reporting done inside GetCluster
return
}
if cluster == nil {
errorHandler.MakeAndReportError("No cluster found", "cluster not found")
return
}

restInfo, err := interfaces.GetSvmByNameDataSource(errorHandler, *client, data.Name.ValueString(), cluster.Version)
if err != nil {
// error reporting done inside GetSvm
return
Expand All @@ -170,6 +185,7 @@ func (d *SvmDataSource) Read(ctx context.Context, req datasource.ReadRequest, re
data.Language = types.StringValue(restInfo.Language)
data.Aggregates = aggregates
data.MaxVolumes = types.StringValue(restInfo.MaxVolumes)
data.StorageLimit = types.Int64Value(int64(restInfo.Storage.Limit))

// Write logs using the tflog package
// Documentation: https://terraform.io/plugin/log
Expand Down
42 changes: 40 additions & 2 deletions internal/provider/svm/svm_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
Expand Down Expand Up @@ -56,6 +57,7 @@ type SvmResourceModel struct {
Language types.String `tfsdk:"language"`
Aggregates []Aggregate `tfsdk:"aggregates"`
MaxVolumes types.String `tfsdk:"max_volumes"`
StorageLimit types.Int64 `tfsdk:"storage_limit"`
ID types.String `tfsdk:"id"`
}

Expand Down Expand Up @@ -120,6 +122,12 @@ func (r *SvmResource) Schema(ctx context.Context, req resource.SchemaRequest, re
MarkdownDescription: "Maximum number of volumes that can be created on the svm. Expects an integer or unlimited",
Optional: true,
},
"storage_limit": schema.Int64Attribute{
MarkdownDescription: "Maximum storage permitted on svm, in bytes",
Optional: true,
Computed: true,
Default: int64default.StaticInt64(0),
Copy link
Contributor

Choose a reason for hiding this comment

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

It is an optional parameter based on the API doc https://docs.netapp.com/us-en/ontap-restapi-9131/ontap/post-svm-svms.html#recommended-optional-properties
The default value is not needed.

Copy link
Author

@acch acch Jan 7, 2025

Choose a reason for hiding this comment

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

Without a default value, the attribute remains "unknown" after the apply - resulting in the following error:

╷
│ Error: Provider returned invalid result object after apply
│ 
│ After the apply operation, the provider still indicated an unknown value for netapp-ontap_svm.example.storage_limit. All values must be known after apply, so this is
│ always a bug in the provider and should be reported in the provider's own repository. Terraform will still save the other known object values in the state.
╵

I could manually set the value to 0 in internal/provider/svm/svm_resource.go > Create(), but isn't a default value in the schema definition easier to read & understand?

},
"id": schema.StringAttribute{
Computed: true,
MarkdownDescription: "SVM identifier",
Expand Down Expand Up @@ -191,6 +199,12 @@ func (r *SvmResource) Create(ctx context.Context, req resource.CreateRequest, re
request.MaxVolumes = data.MaxVolumes.ValueString()
}

setStorageLimitEmpty := true
if !data.StorageLimit.Equal(types.Int64Value(0)) {
setStorageLimitEmpty = false
request.Storage.Limit = int(data.StorageLimit.ValueInt64())
}

setAggrEmpty := false
if len(data.Aggregates) != 0 {
aggregates := []interfaces.Aggregate{}
Expand All @@ -214,7 +228,14 @@ func (r *SvmResource) Create(ctx context.Context, req resource.CreateRequest, re
// error reporting done inside NewClient
return
}
svm, err := interfaces.CreateSvm(errorHandler, *client, request, setAggrEmpty, setCommentEmpty)
svm, err := interfaces.CreateSvm(
errorHandler,
*client,
request,
setAggrEmpty,
setCommentEmpty,
setStorageLimitEmpty,
)
if err != nil {
return
}
Expand Down Expand Up @@ -244,12 +265,23 @@ func (r *SvmResource) Read(ctx context.Context, req resource.ReadRequest, resp *
// error reporting done inside NewClient
return
}

cluster, err := interfaces.GetCluster(errorHandler, *client)
if err != nil {
// error reporting done inside GetCluster
return
}
if cluster == nil {
errorHandler.MakeAndReportError("No cluster found", "cluster not found")
return
}

tflog.Debug(ctx, fmt.Sprintf("read a svm resource: %#v", data))
var svm *interfaces.SvmGetDataSourceModel
if data.ID.ValueString() != "" {
svm, err = interfaces.GetSvm(errorHandler, *client, data.ID.ValueString())
} else {
svm, err = interfaces.GetSvmByNameDataSource(errorHandler, *client, data.Name.ValueString())
svm, err = interfaces.GetSvmByNameDataSource(errorHandler, *client, data.Name.ValueString(), cluster.Version)
}
if err != nil {
return
Expand Down Expand Up @@ -295,6 +327,8 @@ func (r *SvmResource) Read(ctx context.Context, req resource.ReadRequest, resp *
data.MaxVolumes = types.StringValue(svm.MaxVolumes)
}

data.StorageLimit = types.Int64Value(int64(svm.Storage.Limit))

// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
Expand Down Expand Up @@ -375,6 +409,10 @@ func (r *SvmResource) Update(ctx context.Context, req resource.UpdateRequest, re
request.MaxVolumes = data.MaxVolumes.ValueString()
}

if !data.StorageLimit.Equal(state.StorageLimit) {
request.Storage.Limit = int(data.StorageLimit.ValueInt64())
}

// aggregates can be modified as empty list
aggregates := []interfaces.Aggregate{}
if len(data.Aggregates) != 0 {
Expand Down
Loading