diff --git a/CHANGELOG.md b/CHANGELOG.md index abe35e9..ad20993 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## [3.1.1] - 2024-07-16 +### Fixed +- `gp3` volume type support for AWS. +- `volume_throughput` support for AWS GP3 storage volume type. + ## [3.1.0] - 2024-07-15 ### Features - `azure` provider is now supported. diff --git a/docs/data-sources/skysql_service.md b/docs/data-sources/skysql_service.md index 3d8ffd2..cc929d0 100644 --- a/docs/data-sources/skysql_service.md +++ b/docs/data-sources/skysql_service.md @@ -79,10 +79,11 @@ Read-Only: Optional: -- `iops` (Number) The number of IOPS for the storage volume. This is only applicable for io1 volumes. +- `iops` (Number) The number of IOPS for the storage volume. This is only applicable for io1 and gp3 volumes. +- `throughput` (Number) The Throughput for the storage volume. This is only applicable for gp3 volumes. Read-Only: - `size` (Number) The size of the storage volume in GB. -- `volume_type` (String) The type of the storage volume. Possible values are: gp2, io1 etc +- `volume_type` (String) The type of the storage volume. Possible values are: gp3, io1 etc diff --git a/docs/resources/skysql_service.md b/docs/resources/skysql_service.md index 660fa61..257d50c 100644 --- a/docs/resources/skysql_service.md +++ b/docs/resources/skysql_service.md @@ -14,19 +14,21 @@ Creates and manages a service in SkySQL ```terraform # Create a service resource "skysql_service" "default" { - project_id = data.skysql_projects.default.projects[0].id - service_type = "transactional" - topology = "es-single" - cloud_provider = "aws" - region = "us-east-1" - name = "myservice" - architecture = "amd64" - nodes = 1 - size = "sky-2x8" - storage = 100 - ssl_enabled = true - version = data.skysql_versions.default.versions[0].name - volume_type = "gp2" + project_id = data.skysql_projects.default.projects[0].id + service_type = "transactional" + topology = "es-single" + cloud_provider = "aws" + region = "us-east-1" + name = "myservice" + architecture = "amd64" + nodes = 1 + size = "sky-2x8" + storage = 100 + ssl_enabled = true + version = data.skysql_versions.default.versions[0].name + volume_type = "gp3" + volume_iops = 3000 + volume_throughput = 125 # The service create is an asynchronous operation. # if you want to wait for the service to be created set wait_for_creation to true wait_for_creation = true @@ -66,7 +68,8 @@ resource "skysql_service" "default" { - `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts)) - `version` (String) The software version - `volume_iops` (Number) The volume IOPS. This is only applicable for AWS -- `volume_type` (String) The volume type. Valid values are: gp2 and io1. This is only applicable for AWS +- `volume_throughput` (Number) The volume Throughput. This is only applicable for AWS +- `volume_type` (String) The volume type. Valid values are: gp3 and io1. This is only applicable for AWS - `wait_for_creation` (Boolean) Whether to wait for the service to be created. Valid values are: true or false - `wait_for_deletion` (Boolean) Whether to wait for the service to be deleted. Valid values are: true or false - `wait_for_update` (Boolean) Whether to wait for the service to be updated. Valid values are: true or false diff --git a/examples/main.tf b/examples/main.tf index 4afaf1f..73662b9 100644 --- a/examples/main.tf +++ b/examples/main.tf @@ -15,18 +15,20 @@ data "skysql_versions" "default" { resource "skysql_service" "default" { - service_type = "transactional" - topology = "es-single" - cloud_provider = "aws" - region = "us-east-2" - name = "myservice" - architecture = "amd64" - nodes = 1 - size = "sky-2x8" - storage = 100 - ssl_enabled = true - version = data.skysql_versions.default.versions[0].name - volume_type = "gp2" + service_type = "transactional" + topology = "es-single" + cloud_provider = "aws" + region = "us-east-2" + name = "myservice" + architecture = "amd64" + nodes = 1 + size = "sky-2x8" + storage = 100 + ssl_enabled = true + version = data.skysql_versions.default.versions[0].name + volume_type = "gp3" + volume_iops = 3000 + volume_throughput = 125 allow_list = [ { "ip" : "127.0.0.1/32", diff --git a/examples/privateconnect/main.tf b/examples/privateconnect/main.tf index ccfeb84..b297148 100644 --- a/examples/privateconnect/main.tf +++ b/examples/privateconnect/main.tf @@ -22,6 +22,9 @@ resource "skysql_service" "this" { endpoint_mechanism = "privateconnect" endpoint_allowed_accounts = [data.aws_caller_identity.this.account_id] wait_for_creation = true + volume_type = "gp3" + volume_iops = 3000 + volume_throughput = 125 # The following line will be required when tearing down the skysql service # deletion_protection = false } diff --git a/examples/resources/skysql_service.tf b/examples/resources/skysql_service.tf index 460b4a8..2732ea5 100644 --- a/examples/resources/skysql_service.tf +++ b/examples/resources/skysql_service.tf @@ -1,18 +1,20 @@ # Create a service resource "skysql_service" "default" { - project_id = data.skysql_projects.default.projects[0].id - service_type = "transactional" - topology = "es-single" - cloud_provider = "aws" - region = "us-east-1" - name = "myservice" - architecture = "amd64" - nodes = 1 - size = "sky-2x8" - storage = 100 - ssl_enabled = true - version = data.skysql_versions.default.versions[0].name - volume_type = "gp2" + project_id = data.skysql_projects.default.projects[0].id + service_type = "transactional" + topology = "es-single" + cloud_provider = "aws" + region = "us-east-1" + name = "myservice" + architecture = "amd64" + nodes = 1 + size = "sky-2x8" + storage = 100 + ssl_enabled = true + version = data.skysql_versions.default.versions[0].name + volume_type = "gp3" + volume_iops = 3000 + volume_throughput = 125 # The service create is an asynchronous operation. # if you want to wait for the service to be created set wait_for_creation to true wait_for_creation = true diff --git a/internal/provider/service_data_source.go b/internal/provider/service_data_source.go index a3072c8..ef71253 100644 --- a/internal/provider/service_data_source.go +++ b/internal/provider/service_data_source.go @@ -68,6 +68,7 @@ type StorageVolumeDataSourceModel struct { Size types.Int64 `tfsdk:"size"` VolumeType types.String `tfsdk:"volume_type"` IOPS types.Int64 `tfsdk:"iops"` + Throughput types.Int64 `tfsdk:"throughput"` } func (d *ServiceDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { @@ -200,12 +201,17 @@ func (d *ServiceDataSource) Schema(ctx context.Context, req datasource.SchemaReq }, "volume_type": schema.StringAttribute{ Computed: true, - Description: "The type of the storage volume. Possible values are: gp2, io1 etc", + Description: "The type of the storage volume. Possible values are: gp3, io1 etc", }, "iops": schema.Int64Attribute{ Computed: true, Optional: true, - Description: "The number of IOPS for the storage volume. This is only applicable for io1 volumes.", + Description: "The number of IOPS for the storage volume. This is only applicable for io1 and gp3 volumes.", + }, + "throughput": schema.Int64Attribute{ + Computed: true, + Optional: true, + Description: "The Throughput for the storage volume. This is only applicable for gp3 volumes.", }, }, }, @@ -318,6 +324,7 @@ func (d *ServiceDataSource) Read(ctx context.Context, req datasource.ReadRequest Size: types.Int64Value(int64(service.StorageVolume.Size)), VolumeType: types.StringValue(service.StorageVolume.VolumeType), IOPS: types.Int64Value(int64(service.StorageVolume.IOPS)), + Throughput: types.Int64Value(int64(service.StorageVolume.Throughput)), } data.OutboundIps = make([]types.String, len(service.OutboundIps)) diff --git a/internal/provider/service_resource.go b/internal/provider/service_resource.go index 540864f..d012eb3 100644 --- a/internal/provider/service_resource.go +++ b/internal/provider/service_resource.go @@ -78,6 +78,7 @@ type ServiceResourceModel struct { Topology types.String `tfsdk:"topology"` Storage types.Int64 `tfsdk:"storage"` VolumeIOPS types.Int64 `tfsdk:"volume_iops"` + VolumeThroughput types.Int64 `tfsdk:"volume_throughput"` SSLEnabled types.Bool `tfsdk:"ssl_enabled"` NoSQLEnabled types.Bool `tfsdk:"nosql_enabled"` VolumeType types.String `tfsdk:"volume_type"` @@ -221,6 +222,13 @@ var serviceResourceSchemaV0 = schema.Schema{ int64planmodifier.UseStateForUnknown(), }, }, + "volume_throughput": schema.Int64Attribute{ + Optional: true, + Description: "The volume Throughput. This is only applicable for AWS", + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.UseStateForUnknown(), + }, + }, "ssl_enabled": schema.BoolAttribute{ Optional: true, Computed: true, @@ -241,7 +249,7 @@ var serviceResourceSchemaV0 = schema.Schema{ "volume_type": schema.StringAttribute{ Optional: true, Computed: true, - Description: "The volume type. Valid values are: gp2 and io1. This is only applicable for AWS", + Description: "The volume type. Valid values are: gp3 and io1. This is only applicable for AWS", PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), stringplanmodifier.RequiresReplaceIf( @@ -452,6 +460,7 @@ func (r *ServiceResource) Create(ctx context.Context, req resource.CreateRequest Topology: state.Topology.ValueString(), Storage: uint(state.Storage.ValueInt64()), VolumeIOPS: uint(state.VolumeIOPS.ValueInt64()), + VolumeThroughput: uint(state.VolumeThroughput.ValueInt64()), SSLEnabled: state.SSLEnabled.ValueBool(), NoSQLEnabled: state.NoSQLEnabled.ValueBool(), VolumeType: state.VolumeType.ValueString(), @@ -523,6 +532,11 @@ func (r *ServiceResource) Create(ctx context.Context, req resource.CreateRequest } else { state.VolumeIOPS = types.Int64Null() } + if service.StorageVolume.Throughput > 0 { + state.VolumeThroughput = types.Int64Value(int64(service.StorageVolume.Throughput)) + } else { + state.VolumeThroughput = types.Int64Null() + } if service.StorageVolume.VolumeType != "" { state.VolumeType = types.StringValue(service.StorageVolume.VolumeType) } else { @@ -679,6 +693,11 @@ func (r *ServiceResource) readServiceState(ctx context.Context, data *ServiceRes } else { data.VolumeIOPS = types.Int64Null() } + if !data.VolumeThroughput.IsNull() && service.StorageVolume.Throughput > 0 { + data.VolumeThroughput = types.Int64Value(int64(service.StorageVolume.Throughput)) + } else { + data.VolumeThroughput = types.Int64Null() + } data.VolumeType = types.StringValue(service.StorageVolume.VolumeType) if !data.ReplicationEnabled.IsNull() { data.ReplicationEnabled = types.BoolValue(service.ReplicationEnabled) @@ -805,16 +824,18 @@ func (r *ServiceResource) updateAllowListState(plan *ServiceResourceModel, state } func (r *ServiceResource) updateServiceStorage(ctx context.Context, plan *ServiceResourceModel, state *ServiceResourceModel, resp *resource.UpdateResponse) { - if plan.Storage.ValueInt64() != state.Storage.ValueInt64() || plan.VolumeIOPS.ValueInt64() != state.VolumeIOPS.ValueInt64() { + if plan.Storage.ValueInt64() != state.Storage.ValueInt64() || plan.VolumeIOPS.ValueInt64() != state.VolumeIOPS.ValueInt64() || plan.VolumeThroughput.ValueInt64() != state.VolumeThroughput.ValueInt64() { tflog.Info(ctx, "Updating storage size for the service", map[string]interface{}{ - "id": state.ID.ValueString(), - "from": state.Storage.ValueInt64(), - "to": plan.Storage.ValueInt64(), - "iops_from": state.VolumeIOPS.ValueInt64(), - "iops_to": plan.VolumeIOPS.ValueInt64(), + "id": state.ID.ValueString(), + "from": state.Storage.ValueInt64(), + "to": plan.Storage.ValueInt64(), + "iops_from": state.VolumeIOPS.ValueInt64(), + "iops_to": plan.VolumeIOPS.ValueInt64(), + "throughput_from": state.VolumeThroughput.ValueInt64(), + "throughput_to": plan.VolumeThroughput.ValueInt64(), }) - err := r.client.ModifyServiceStorage(ctx, state.ID.ValueString(), plan.Storage.ValueInt64(), plan.VolumeIOPS.ValueInt64()) + err := r.client.ModifyServiceStorage(ctx, state.ID.ValueString(), plan.Storage.ValueInt64(), plan.VolumeIOPS.ValueInt64(), plan.VolumeThroughput.ValueInt64()) if err != nil { resp.Diagnostics.AddError("Error updating a storage for the service", fmt.Sprintf("Unable to update a storage size for the service, got error: %s", err)) @@ -1148,19 +1169,42 @@ func (r *ServiceResource) ModifyPlan(ctx context.Context, req resource.ModifyPla } if plan.Provider.ValueString() == "aws" { - if !plan.VolumeIOPS.IsNull() && plan.VolumeType.IsNull() { + + if plan.VolumeType.IsNull() { resp.Diagnostics.AddAttributeError(path.Root("volume_type"), - "volume_type is require", - "volume_type is required when volume_iops is set. "+ - "Use: io1 for volume_type if volume_iops is set") + "volume_type is required", + "volume_type is required for AWS. Use: io1 or gp3 for volume_type.") return } - if !plan.VolumeIOPS.IsNull() && plan.VolumeType.ValueString() != "io1" { + + if plan.VolumeType.ValueString() != "io1" && plan.VolumeType.ValueString() != "gp3" { resp.Diagnostics.AddAttributeError(path.Root("volume_type"), - "volume_type must be io1 when you want to set IOPS", - "Use: io1 for volume_type if volume_iops is set") + "volume_type is not supported", + "volume_type provided is not supported. Use: io1 or gp3 for volume_type.") return } + + if plan.VolumeIOPS.IsNull() { + resp.Diagnostics.AddAttributeError(path.Root("volume_iops"), + "volume_iops are required", + "volume_iops are required for AWS") + return + } + + if plan.VolumeType.ValueString() == "io1" && !plan.VolumeThroughput.IsNull() { + resp.Diagnostics.AddAttributeError(path.Root("volume_throughput"), + "volume_throughput is not supported for io1", + "volume_throughput is supported only for gp3 volume_type for AWS") + return + } + + if plan.VolumeType.ValueString() == "gp3" && plan.VolumeThroughput.IsNull() { + resp.Diagnostics.AddAttributeError(path.Root("volume_throughput"), + "volume_throughput is required", + "volume_throughput is required for gp3 volume_type for AWS") + return + } + } else if plan.Provider.ValueString() == "gcp" { if !(plan.VolumeType.ValueString() == "" || plan.VolumeType.IsNull() || plan.VolumeType.ValueString() == "pd-ssd") { resp.Diagnostics.AddAttributeError( diff --git a/internal/provider/service_resource_deletion_protection_test.go b/internal/provider/service_resource_deletion_protection_test.go index 54c9e67..be48bc9 100644 --- a/internal/provider/service_resource_deletion_protection_test.go +++ b/internal/provider/service_resource_deletion_protection_test.go @@ -83,10 +83,12 @@ func TestServiceResourceDeletionProtection(t *testing.T) { Size int `json:"size"` VolumeType string `json:"volume_type"` IOPS int `json:"iops"` + Throughput int `json:"throughput"` }{ Size: int(payload.Storage), VolumeType: payload.VolumeType, IOPS: int(payload.VolumeIOPS), + Throughput: int(payload.VolumeThroughput), }, OutboundIps: nil, IsActive: true, diff --git a/internal/provider/service_resource_privatlink_test.go b/internal/provider/service_resource_privatlink_test.go index 9eb6e71..72cef85 100644 --- a/internal/provider/service_resource_privatlink_test.go +++ b/internal/provider/service_resource_privatlink_test.go @@ -82,10 +82,12 @@ func TestServiceResourcePrivateLink(t *testing.T) { Size int `json:"size"` VolumeType string `json:"volume_type"` IOPS int `json:"iops"` + Throughput int `json:"throughput"` }{ Size: int(payload.Storage), VolumeType: payload.VolumeType, IOPS: int(payload.VolumeIOPS), + Throughput: int(payload.VolumeThroughput), }, OutboundIps: nil, IsActive: true, @@ -393,10 +395,12 @@ func TestServiceResourcePrivateConnectWhenAllowedAccountsEmpty(t *testing.T) { Size int `json:"size"` VolumeType string `json:"volume_type"` IOPS int `json:"iops"` + Throughput int `json:"throughput"` }{ Size: int(payload.Storage), VolumeType: payload.VolumeType, IOPS: int(payload.VolumeIOPS), + Throughput: int(payload.VolumeThroughput), }, OutboundIps: nil, IsActive: true, diff --git a/internal/provider/service_resource_sa_test.go b/internal/provider/service_resource_sa_test.go index 593f8eb..9f7de64 100644 --- a/internal/provider/service_resource_sa_test.go +++ b/internal/provider/service_resource_sa_test.go @@ -86,6 +86,16 @@ func TestServiceResourceServerlessAnalytics(t *testing.T) { Mechanism: "nlb", }, }, + StorageVolume: struct { + Size int `json:"size"` + VolumeType string `json:"volume_type"` + IOPS int `json:"iops"` + Throughput int `json:"throughput"` + }{ + Size: int(payload.Storage), + VolumeType: payload.VolumeType, + IOPS: int(payload.VolumeIOPS), + }, IsActive: true, ServiceType: payload.ServiceType, } @@ -144,6 +154,8 @@ func TestServiceResourceServerlessAnalytics(t *testing.T) { wait_for_update = true deletion_protection = false nodes = 1 + volume_type = "io1" + volume_iops = 3000 } `, ExpectError: regexp.MustCompile(`The argument "nodes" is read only for the "sa" topology`), @@ -166,6 +178,8 @@ func TestServiceResourceServerlessAnalytics(t *testing.T) { wait_for_deletion = true wait_for_update = true deletion_protection = false + volume_type = "io1" + volume_iops = 3000 } `, ExpectError: regexp.MustCompile(`The argument "architecture" is read only for the "sa" topology`), @@ -184,6 +198,8 @@ func TestServiceResourceServerlessAnalytics(t *testing.T) { wait_for_deletion = true wait_for_update = true deletion_protection = false + volume_type = "io1" + volume_iops = 3000 } `, ExpectError: regexp.MustCompile(`The argument "size" is read only for the "sa" topology`), @@ -202,6 +218,8 @@ func TestServiceResourceServerlessAnalytics(t *testing.T) { wait_for_deletion = true wait_for_update = true deletion_protection = false + volume_type = "io1" + volume_iops = 3000 } `, ExpectError: regexp.MustCompile(`The argument "ssl_enabled" is read only for the "sa" topology`), @@ -220,6 +238,8 @@ func TestServiceResourceServerlessAnalytics(t *testing.T) { wait_for_deletion = true wait_for_update = true deletion_protection = false + volume_type = "io1" + volume_iops = 3000 } `, ExpectError: regexp.MustCompile(`The argument "version" is read only for the "sa" topology`), @@ -237,6 +257,8 @@ func TestServiceResourceServerlessAnalytics(t *testing.T) { wait_for_deletion = true wait_for_update = true deletion_protection = false + volume_type = "io1" + volume_iops = 3000 } `, Check: resource.ComposeAggregateTestCheckFunc([]resource.TestCheckFunc{ @@ -256,6 +278,8 @@ func TestServiceResourceServerlessAnalytics(t *testing.T) { wait_for_deletion = true wait_for_update = true deletion_protection = false + volume_type = "io1" + volume_iops = 3000 } `, ExpectError: regexp.MustCompile(`Cannot change service architecture`), @@ -274,6 +298,8 @@ func TestServiceResourceServerlessAnalytics(t *testing.T) { wait_for_deletion = true wait_for_update = true deletion_protection = false + volume_type = "io1" + volume_iops = 3000 } `, ExpectError: regexp.MustCompile(`Attempt to modify read-only attribute`), @@ -292,6 +318,8 @@ func TestServiceResourceServerlessAnalytics(t *testing.T) { wait_for_deletion = true wait_for_update = true deletion_protection = false + volume_type = "io1" + volume_iops = 3000 } `, ExpectError: regexp.MustCompile(`Attempt to modify read-only attribute`), @@ -310,6 +338,8 @@ func TestServiceResourceServerlessAnalytics(t *testing.T) { wait_for_deletion = true wait_for_update = true deletion_protection = false + volume_type = "io1" + volume_iops = 3000 } `, ExpectError: regexp.MustCompile(`Cannot change service ssl_enabled`), @@ -328,6 +358,8 @@ func TestServiceResourceServerlessAnalytics(t *testing.T) { wait_for_deletion = true wait_for_update = true deletion_protection = false + volume_type = "io1" + volume_iops = 3000 } `, ExpectError: regexp.MustCompile(`Cannot change service version`), diff --git a/internal/provider/service_resource_scale_test.go b/internal/provider/service_resource_scale_test.go index 5dcc2c3..30d1920 100644 --- a/internal/provider/service_resource_scale_test.go +++ b/internal/provider/service_resource_scale_test.go @@ -80,6 +80,7 @@ func TestServiceResourceScaleTest(t *testing.T) { Size int `json:"size"` VolumeType string `json:"volume_type"` IOPS int `json:"iops"` + Throughput int `json:"throughput"` }{ Size: int(payload.Storage), VolumeType: payload.VolumeType, @@ -241,6 +242,7 @@ func TestServiceResourceScaleTest(t *testing.T) { size = "sky-2x8" storage = 100 volume_type = "io1" + volume_iops = 3000 ssl_enabled = true version = "10.6.11-6-1" wait_for_creation = true @@ -266,7 +268,7 @@ func TestServiceResourceScaleTest(t *testing.T) { size = "sky-4x16" storage = 200 volume_type = "io1" - volume_iops = "1" + volume_iops = 1000 ssl_enabled = true version = "10.6.11-6-1" wait_for_creation = true diff --git a/internal/provider/service_resource_test.go b/internal/provider/service_resource_test.go index 0bba39d..ca92e2a 100644 --- a/internal/provider/service_resource_test.go +++ b/internal/provider/service_resource_test.go @@ -149,10 +149,12 @@ resource "skysql_service" default { Size int `json:"size"` VolumeType string `json:"volume_type"` IOPS int `json:"iops"` + Throughput int `json:"throughput"` }{ Size: int(payload.Storage), VolumeType: payload.VolumeType, IOPS: int(payload.VolumeIOPS), + Throughput: int(payload.VolumeThroughput), }, OutboundIps: nil, IsActive: true, @@ -375,10 +377,12 @@ resource "skysql_service" default { Size int `json:"size"` VolumeType string `json:"volume_type"` IOPS int `json:"iops"` + Throughput int `json:"throughput"` }{ Size: int(payload.Storage), VolumeType: payload.VolumeType, IOPS: int(payload.VolumeIOPS), + Throughput: int(payload.VolumeThroughput), }, OutboundIps: nil, IsActive: false, @@ -522,10 +526,12 @@ resource "skysql_service" default { Size int `json:"size"` VolumeType string `json:"volume_type"` IOPS int `json:"iops"` + Throughput int `json:"throughput"` }{ Size: int(payload.Storage), VolumeType: payload.VolumeType, IOPS: int(payload.VolumeIOPS), + Throughput: int(payload.VolumeThroughput), }, OutboundIps: nil, IsActive: true, @@ -600,7 +606,7 @@ resource "skysql_service" default { expectError: regexp.MustCompile(`Invalid provider value`), }, { - name: "create service when unexpected volume_type is not set for volume_iops", + name: "volume_type required for aws", testResource: fmt.Sprintf(` resource "skysql_service" default { service_type = "transactional" @@ -609,7 +615,6 @@ resource "skysql_service" default { region = "us-central1" name = "%s" architecture = "amd64" - volume_iops = 100 nodes = 1 size = "sky-2x8" storage = 100 @@ -631,7 +636,146 @@ resource "skysql_service" default { w.WriteHeader(http.StatusOK) }) }, - expectError: regexp.MustCompile(`volume_type must be io1 when you want to set IOPS`), + expectError: regexp.MustCompile(`volume_type provided is not supported. Use: io1 or gp3 for volume_type.`), + }, + { + name: "invalid volume_type for aws", + testResource: fmt.Sprintf(` + resource "skysql_service" default { + service_type = "transactional" + topology = "es-single" + cloud_provider = "aws" + region = "us-central1" + name = "%s" + architecture = "amd64" + nodes = 1 + size = "sky-2x8" + storage = 100 + ssl_enabled = true + version = "10.6.11-6-1" + wait_for_creation = true + wait_for_deletion = true + deletion_protection = false + volume_type = "gp2" + } + `, GenerateServiceName(t)), + before: func(r *require.Assertions) { + configureOnce.Reset() + expectRequest(func(w http.ResponseWriter, req *http.Request) { + r.Equal(http.MethodGet, req.Method) + r.Equal("/provisioning/v1/versions", req.URL.Path) + r.Equal("page_size=1", req.URL.RawQuery) + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode([]provisioning.Version{}) + w.WriteHeader(http.StatusOK) + }) + }, + expectError: regexp.MustCompile(`volume_type provided is not supported. Use: io1 or gp3 for volume_type.`), + }, + { + name: "volume_iops are required for aws", + testResource: fmt.Sprintf(` + resource "skysql_service" default { + service_type = "transactional" + topology = "es-single" + cloud_provider = "aws" + region = "us-central1" + name = "%s" + architecture = "amd64" + nodes = 1 + size = "sky-2x8" + storage = 100 + ssl_enabled = true + version = "10.6.11-6-1" + wait_for_creation = true + wait_for_deletion = true + deletion_protection = false + volume_type = "io1" + } + `, GenerateServiceName(t)), + before: func(r *require.Assertions) { + configureOnce.Reset() + expectRequest(func(w http.ResponseWriter, req *http.Request) { + r.Equal(http.MethodGet, req.Method) + r.Equal("/provisioning/v1/versions", req.URL.Path) + r.Equal("page_size=1", req.URL.RawQuery) + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode([]provisioning.Version{}) + w.WriteHeader(http.StatusOK) + }) + }, + expectError: regexp.MustCompile(`volume_iops are required for AWS`), + }, + { + name: "volume_throughput is not supported for io1", + testResource: fmt.Sprintf(` + resource "skysql_service" default { + service_type = "transactional" + topology = "es-single" + cloud_provider = "aws" + region = "us-central1" + name = "%s" + architecture = "amd64" + nodes = 1 + size = "sky-2x8" + storage = 100 + ssl_enabled = true + version = "10.6.11-6-1" + wait_for_creation = true + wait_for_deletion = true + deletion_protection = false + volume_type = "io1" + volume_iops = 3000 + volume_throughput = 125 + } + `, GenerateServiceName(t)), + before: func(r *require.Assertions) { + configureOnce.Reset() + expectRequest(func(w http.ResponseWriter, req *http.Request) { + r.Equal(http.MethodGet, req.Method) + r.Equal("/provisioning/v1/versions", req.URL.Path) + r.Equal("page_size=1", req.URL.RawQuery) + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode([]provisioning.Version{}) + w.WriteHeader(http.StatusOK) + }) + }, + expectError: regexp.MustCompile(`volume_throughput is supported only for gp3 volume_type for AWS`), + }, + { + name: "volume_throughput is required for gp3", + testResource: fmt.Sprintf(` + resource "skysql_service" default { + service_type = "transactional" + topology = "es-single" + cloud_provider = "aws" + region = "us-central1" + name = "%s" + architecture = "amd64" + nodes = 1 + size = "sky-2x8" + storage = 100 + ssl_enabled = true + version = "10.6.11-6-1" + wait_for_creation = true + wait_for_deletion = true + deletion_protection = false + volume_type = "gp3" + volume_iops = 3000 + } + `, GenerateServiceName(t)), + before: func(r *require.Assertions) { + configureOnce.Reset() + expectRequest(func(w http.ResponseWriter, req *http.Request) { + r.Equal(http.MethodGet, req.Method) + r.Equal("/provisioning/v1/versions", req.URL.Path) + r.Equal("page_size=1", req.URL.RawQuery) + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode([]provisioning.Version{}) + w.WriteHeader(http.StatusOK) + }) + }, + expectError: regexp.MustCompile(`volume_throughput is required for gp3 volume_type for AWS`), }, } diff --git a/internal/provider/service_resource_update_allowlist_test.go b/internal/provider/service_resource_update_allowlist_test.go index d8aca2f..6b89ce7 100644 --- a/internal/provider/service_resource_update_allowlist_test.go +++ b/internal/provider/service_resource_update_allowlist_test.go @@ -85,10 +85,12 @@ func TestServiceResourceAllowlistUpdate(t *testing.T) { Size int `json:"size"` VolumeType string `json:"volume_type"` IOPS int `json:"iops"` + Throughput int `json:"throughput"` }{ Size: int(payload.Storage), VolumeType: payload.VolumeType, IOPS: int(payload.VolumeIOPS), + Throughput: int(payload.VolumeThroughput), }, OutboundIps: nil, IsActive: true, diff --git a/internal/provider/service_resource_volume_type_test.go b/internal/provider/service_resource_volume_type_test.go index 02cc8a6..8261ca1 100644 --- a/internal/provider/service_resource_volume_type_test.go +++ b/internal/provider/service_resource_volume_type_test.go @@ -81,10 +81,12 @@ func TestServiceResourceGCPVolumeType(t *testing.T) { Size int `json:"size"` VolumeType string `json:"volume_type"` IOPS int `json:"iops"` + Throughput int `json:"throughput"` }{ Size: int(payload.Storage), VolumeType: payload.VolumeType, IOPS: int(payload.VolumeIOPS), + Throughput: int(payload.VolumeThroughput), }, OutboundIps: nil, IsActive: true, @@ -162,7 +164,7 @@ func TestServiceResourceGCPVolumeType(t *testing.T) { }) } -func TestServiceResourceAWSGP2VolumeType(t *testing.T) { +func TestServiceResourceAWSGP3VolumeType(t *testing.T) { const serviceID = "dbdgf42002418" testURL, expectRequest, closeAPI := mockSkySQLAPI(t) @@ -227,10 +229,12 @@ func TestServiceResourceAWSGP2VolumeType(t *testing.T) { Size int `json:"size"` VolumeType string `json:"volume_type"` IOPS int `json:"iops"` + Throughput int `json:"throughput"` }{ Size: int(payload.Storage), VolumeType: payload.VolumeType, IOPS: int(payload.VolumeIOPS), + Throughput: int(payload.VolumeThroughput), }, OutboundIps: nil, IsActive: true, @@ -295,13 +299,15 @@ func TestServiceResourceAWSGP2VolumeType(t *testing.T) { storage = 100 ssl_enabled = true version = "10.6.11-6-1" - volume_type = "gp2" + volume_type = "gp3" + volume_iops = 3000 + volume_throughput = 125 deletion_protection = "false" } `, Check: resource.ComposeAggregateTestCheckFunc([]resource.TestCheckFunc{ resource.TestCheckResourceAttr("skysql_service.default", "id", serviceID), - resource.TestCheckResourceAttr("skysql_service.default", "volume_type", "gp2"), + resource.TestCheckResourceAttr("skysql_service.default", "volume_type", "gp3"), }...), }, }, @@ -373,10 +379,12 @@ func TestServiceResourceAWSIO1VolumeType(t *testing.T) { Size int `json:"size"` VolumeType string `json:"volume_type"` IOPS int `json:"iops"` + Throughput int `json:"throughput"` }{ Size: int(payload.Storage), VolumeType: payload.VolumeType, IOPS: int(payload.VolumeIOPS), + Throughput: int(payload.VolumeThroughput), }, OutboundIps: nil, IsActive: true, @@ -442,6 +450,7 @@ func TestServiceResourceAWSIO1VolumeType(t *testing.T) { ssl_enabled = true version = "10.6.11-6-1" volume_type = "io1" + volume_iops = 3000 deletion_protection = "false" } `, @@ -519,10 +528,12 @@ func TestServiceResourceAzureVolumeType(t *testing.T) { Size int `json:"size"` VolumeType string `json:"volume_type"` IOPS int `json:"iops"` + Throughput int `json:"throughput"` }{ Size: int(payload.Storage), VolumeType: payload.VolumeType, IOPS: int(payload.VolumeIOPS), + Throughput: int(payload.VolumeThroughput), }, OutboundIps: nil, IsActive: true, diff --git a/internal/skysql/client.go b/internal/skysql/client.go index 4f1f179..3030d7d 100644 --- a/internal/skysql/client.go +++ b/internal/skysql/client.go @@ -300,11 +300,11 @@ func (c *Client) ModifyServiceNodeNumber(ctx context.Context, serviceID string, return err } -func (c *Client) ModifyServiceStorage(ctx context.Context, serviceID string, size int64, iops int64) error { +func (c *Client) ModifyServiceStorage(ctx context.Context, serviceID string, size int64, iops int64, throughput int64) error { resp, err := c.HTTPClient.R(). SetHeader("Accept", "application/json"). SetContext(ctx). - SetBody(&provisioning.UpdateStorageRequest{Size: size, IOPS: iops}). + SetBody(&provisioning.UpdateStorageRequest{Size: size, IOPS: iops, Throughput: throughput}). SetError(&ErrorResponse{}). Patch("/provisioning/v1/services/" + serviceID + "/storage") if err != nil { diff --git a/internal/skysql/provisioning/create_service_request.go b/internal/skysql/provisioning/create_service_request.go index 2da2828..d02dffd 100644 --- a/internal/skysql/provisioning/create_service_request.go +++ b/internal/skysql/provisioning/create_service_request.go @@ -13,6 +13,7 @@ type CreateServiceRequest struct { Topology string `json:"topology"` Storage uint `json:"storage"` VolumeIOPS uint `json:"volume_iops"` + VolumeThroughput uint `json:"volume_throughput"` SSLEnabled bool `json:"ssl_enabled"` NoSQLEnabled bool `json:"nosql_enabled"` VolumeType string `json:"volume_type,omitempty"` diff --git a/internal/skysql/provisioning/service.go b/internal/skysql/provisioning/service.go index b215e99..8280d84 100644 --- a/internal/skysql/provisioning/service.go +++ b/internal/skysql/provisioning/service.go @@ -24,6 +24,7 @@ type Service struct { Size int `json:"size"` VolumeType string `json:"volume_type"` IOPS int `json:"iops"` + Throughput int `json:"throughput"` } `json:"storage_volume"` OutboundIps []string `json:"outbound_ips"` IsActive bool `json:"is_active"` diff --git a/internal/skysql/provisioning/update_storage.go b/internal/skysql/provisioning/update_storage.go index 70887da..a6a4df9 100644 --- a/internal/skysql/provisioning/update_storage.go +++ b/internal/skysql/provisioning/update_storage.go @@ -1,6 +1,7 @@ package provisioning type UpdateStorageRequest struct { - Size int64 `json:"size,omitempty"` - IOPS int64 `json:"iops,omitempty"` + Size int64 `json:"size,omitempty"` + IOPS int64 `json:"iops,omitempty"` + Throughput int64 `json:"throughput,omitempty"` }