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
Original file line number Diff line number Diff line change
Expand Up @@ -205,12 +205,9 @@ func diskSizeCutomizeDiff(ctx context.Context, d *schema.ResourceDiff, meta inte
return nil
}

// If we are here, we are trying to shrink the disk with auto resize disabled and no ignore changes on disk size.
// This will force a new resource.
if err := d.ForceNew(key); err != nil {
return err
}

// If we are here, we are shrinking the disk with auto resize disabled. Keep the diff so
// the update performs an in-place storage shrink (Instances.PerformDiskShrink) rather than
// recreating the instance.
return nil
}

Expand Down Expand Up @@ -576,7 +573,7 @@ API (for read pools, effective_availability_type may differ from availability_ty
Type: schema.TypeInt,
Optional: true,
Computed: true,
Description: `The size of data disk, in GB. Size of a running instance cannot be reduced but can be increased. The minimum value is 10GB for PD_SSD, PD_HDD and 20GB for HYPERDISK_BALANCED.`,
Description: `The size of data disk, in GB. The size of a running instance can be increased, or reduced when disk_autoresize is disabled (this triggers an in-place storage shrink, which restarts the instance). The minimum value is 10GB for PD_SSD, PD_HDD and 20GB for HYPERDISK_BALANCED.`,
},
"disk_type": {
Type: schema.TypeString,
Expand Down Expand Up @@ -2973,17 +2970,17 @@ func resourceSqlDatabaseInstanceUpdate(d *schema.ResourceData, meta interface{})
Settings: expandSqlDatabaseInstanceSettings(desiredSetting.([]interface{}), databaseVersion),
}

// A disk_size decrease is a storage shrink, which is carried out by the dedicated
// Instances.PerformDiskShrink operation below rather than by the settings update. For a
// shrink, keep the current (larger) size on the update call so disk size is unchanged
// there, and record that a shrink must be performed. Increases are already applied via
// the expanded settings (DataDiskSizeGb) on the update call.
diskShrink := false
if d.HasChange("settings.0.disk_size") {
autoResize := true
_, autoResizeI := d.GetChange("settings.0.disk_autoresize")
if autoResizeI != nil {
autoResize = autoResizeI.(bool)
}
oldDiskSizeI, newDiskSizeI := d.GetChange("settings.0.disk_size")
if !autoResize || newDiskSizeI.(int) > oldDiskSizeI.(int) {
// If auto resize is not enabled - set the disk size as requested, even if it's a decrease - let it fail.
// Otherwise, allow increasing even if auto resize is enabled.
instance.Settings.DataDiskSizeGb = int64(newDiskSizeI.(int))
if newDiskSizeI.(int) < oldDiskSizeI.(int) {
diskShrink = true
instance.Settings.DataDiskSizeGb = int64(oldDiskSizeI.(int))
}
}

Expand Down Expand Up @@ -3065,6 +3062,51 @@ func resourceSqlDatabaseInstanceUpdate(d *schema.ResourceData, meta interface{})
return err
}

// Perform an in-place storage shrink if disk_size was reduced. This is a dedicated,
// disruptive operation (the instance restarts) and is run after the settings update.
if diskShrink {
name := d.Get("name").(string)
targetSizeGb := int64(d.Get("settings.0.disk_size").(int))

// Pre-flight: the smallest size an instance can shrink to depends on its current
// storage usage. Fetch that minimum and surface a clear error up front instead of
// letting the shrink operation start and then fail.
var shrinkConfig *sqladmin.SqlInstancesGetDiskShrinkConfigResponse
err = transport_tpg.Retry(transport_tpg.RetryOptions{
RetryFunc: func() (rerr error) {
shrinkConfig, rerr = NewClient(config, userAgent).Projects.Instances.GetDiskShrinkConfig(project, name).Do()
return rerr
},
Timeout: d.Timeout(schema.TimeoutUpdate),
ErrorRetryPredicates: []transport_tpg.RetryErrorPredicateFunc{transport_tpg.IsSqlOperationInProgressError},
})
if err != nil {
return fmt.Errorf("Error, failed to get storage shrink config for %s: %s", name, err)
}
if targetSizeGb < shrinkConfig.MinimalTargetSizeGb {
return fmt.Errorf("Error, cannot shrink storage of %s to %d GB: the minimum size this instance can currently shrink to is %d GB", name, targetSizeGb, shrinkConfig.MinimalTargetSizeGb)
}

shrinkContext := &sqladmin.PerformDiskShrinkContext{
TargetSizeGb: targetSizeGb,
}
err = transport_tpg.Retry(transport_tpg.RetryOptions{
RetryFunc: func() (rerr error) {
op, rerr = NewClient(config, userAgent).Projects.Instances.PerformDiskShrink(project, name, shrinkContext).Do()
return rerr
},
Timeout: d.Timeout(schema.TimeoutUpdate),
ErrorRetryPredicates: []transport_tpg.RetryErrorPredicateFunc{transport_tpg.IsSqlOperationInProgressError},
})
if err != nil {
return fmt.Errorf("Error, failed to perform storage shrink for %s: %s", name, err)
}
err = SqlAdminOperationWaitTime(config, op, project, "Perform Disk Shrink", userAgent, d.Timeout(schema.TimeoutUpdate))
if err != nil {
return err
}
}

// Perform a backup restore if the backup context exists and has changed
if r, ok := d.GetOk("restore_backup_context"); ok {
if d.HasChange("restore_backup_context") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4860,9 +4860,12 @@ func TestAccSqlDatabaseInstance_DiskSizeAutoResizeWithDiskSize(t *testing.T) {
Check: testGoogleSqlDatabaseInstanceCheckDiskSize(t, databaseName, 15),
},
{
// Disk size is now 15gb - requested 14gb, but disabled auto resize - should error because it can't be deleted for replacement.
Config: testGoogleSqlDatabaseInstance_diskSizeAutoResize(project, databaseName, 14, 104, &falseVar, false, false),
ExpectError: regexp.MustCompile("Instance cannot be destroyed"),
// Disk size is now 15gb - requested 14gb with auto resize disabled. A shrink is now an
// in-place storage shrink (an update), not a destroy/recreate, so prevent_destroy no
// longer blocks the plan and the plan is a non-empty (update) diff.
Config: testGoogleSqlDatabaseInstance_diskSizeAutoResize(project, databaseName, 14, 104, &falseVar, false, false),
PlanOnly: true,
ExpectNonEmptyPlan: true,
},
{
// Disk size is now 15gb - requested 14gb, but ignore changes is set - so should ignore the configuration change.
Expand All @@ -4877,6 +4880,39 @@ func TestAccSqlDatabaseInstance_DiskSizeAutoResizeWithDiskSize(t *testing.T) {
})
}

func TestAccSqlDatabaseInstance_storageShrink(t *testing.T) {
t.Parallel()

databaseName := "tf-test-" + acctest.RandString(t, 10)
var instanceID string

acctest.VcrTest(t, resource.TestCase{
PreCheck: func() { acctest.AccTestPreCheck(t) },
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
CheckDestroy: testAccSqlDatabaseInstanceDestroyProducer(t),
Steps: []resource.TestStep{
{
// Create a non-shared-core instance with a 100GB disk and auto resize disabled
// (storage shrink is not supported on shared-core machine types). The starting
// size is well above the instance's minimum shrinkable size.
Config: testGoogleSqlDatabaseInstance_storageShrink(databaseName, 100),
Check: resource.ComposeTestCheckFunc(
testGoogleSqlDatabaseInstanceCheckDiskSize(t, databaseName, 100),
testAccSqlDatabaseInstanceCaptureID(&instanceID),
),
},
{
// Reduce disk_size to 90GB. This must be an in-place storage shrink (no recreate).
Config: testGoogleSqlDatabaseInstance_storageShrink(databaseName, 90),
Check: resource.ComposeTestCheckFunc(
testGoogleSqlDatabaseInstanceCheckDiskSize(t, databaseName, 90),
testAccSqlDatabaseInstanceCheckSameID(&instanceID),
),
},
},
})
}

func TestEnhancedBackupManagerDiffSuppressFunc(t *testing.T) {
cases := map[string]struct {
key string
Expand Down Expand Up @@ -10343,6 +10379,47 @@ func testGoogleSqlDatabaseInstanceResizeDisk(t *testing.T, instance string, addG
}
}

func testGoogleSqlDatabaseInstance_storageShrink(dbName string, diskSize int) string {
return fmt.Sprintf(`
resource "google_sql_database_instance" "instance" {
name = "%s"
region = "us-central1"
database_version = "POSTGRES_15"
deletion_protection = false
settings {
tier = "db-custom-2-8192"
disk_type = "PD_SSD"
disk_size = %d
disk_autoresize = false
}
}
`, dbName, diskSize)
}

func testAccSqlDatabaseInstanceCaptureID(id *string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources["google_sql_database_instance.instance"]
if !ok {
return fmt.Errorf("resource google_sql_database_instance.instance not found in state")
}
*id = rs.Primary.ID
return nil
}
}

func testAccSqlDatabaseInstanceCheckSameID(id *string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources["google_sql_database_instance.instance"]
if !ok {
return fmt.Errorf("resource google_sql_database_instance.instance not found in state")
}
if rs.Primary.ID != *id {
return fmt.Errorf("instance was recreated during storage shrink: id changed from %q to %q", *id, rs.Primary.ID)
}
return nil
}
}

func testGoogleSqlDatabaseInstanceCheckDiskSize(t *testing.T, instance string, size int64) resource.TestCheckFunc {
return func(s *terraform.State) error {
config := acctest.GoogleProviderConfig(t)
Expand Down