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

Project: Unified Migration #1201

Merged
merged 6 commits into from
Jan 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/resources/image.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ The following arguments apply to uploading an image:

### Timeouts

The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions:
The `timeouts` block allows you to specify [timeouts](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts) for certain actions:

* `create` - (Defaults to 20 mins) Used when creating the instance image (until the instance is available)

Expand Down
8 changes: 5 additions & 3 deletions docs/resources/instance.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ resource "linode_instance_config" "boot_config" {

The following arguments are supported:

* `region` - (Required) This is the location where the Linode is deployed. Examples are `"us-east"`, `"us-west"`, `"ap-south"`, etc. See all regions [here](https://api.linode.com/v4/regions). *Changing `region` forces the creation of a new Linode Instance.*.
* `region` - (Required) This is the location where the Linode is deployed. Examples are `"us-east"`, `"us-west"`, `"ap-south"`, etc. See all regions [here](https://api.linode.com/v4/regions). *Changing `region` will trigger a migration of this Linode. Migration operations are typically long-running operations, so the [update timeout](#timeouts) should be adjusted accordingly.*.

* `type` - (Required) The Linode type defines the pricing, CPU, disk, and RAM specs of the instance. Examples are `"g6-nanode-1"`, `"g6-standard-2"`, `"g6-highmem-16"`, `"g6-dedicated-16"`, etc. See all types [here](https://api.linode.com/v4/linode/types).

Expand Down Expand Up @@ -130,6 +130,8 @@ The following arguments are supported:

* `booted` - (Optional) If true, then the instance is kept or converted into in a running state. If false, the instance will be shutdown. If unspecified, the Linode's power status will not be managed by the Provider.

* `migration_type` - (Optional) The type of migration to use when updating the type or region of a Linode. (`cold`, `warm`; default `cold`)

* [`interface`](#interface) - (Optional) A list of network interfaces to be assigned to the Linode on creation. If an explicit config or disk is defined, interfaces must be declared in the [`config` block](#configs).

### Simplified Resource Arguments
Expand Down Expand Up @@ -277,10 +279,10 @@ The following arguments are available in an `ipv4` configuration block of an `in

### Timeouts

The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions:
The `timeouts` block allows you to specify [timeouts](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts) for certain actions:

* `create` - (Defaults to 10 mins) Used when launching the instance (until it reaches the initial `running` state)
* `update` - (Defaults to 20 mins) Used when stopping and starting the instance when necessary during update - e.g. when changing instance type
* `update` - (Defaults to 1 hour) Used when stopping and starting the instance when necessary during update - e.g. when changing instance type
* `delete` - (Defaults to 10 mins) Used when terminating the instance

## Attributes Reference
Expand Down
2 changes: 1 addition & 1 deletion docs/resources/volume.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ The following arguments are supported:

### Timeouts

The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions:
The `timeouts` block allows you to specify [timeouts](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts) for certain actions:

* `create` - (Defaults to 10 mins) Used when creating the volume (until the volume is reaches the initial `active` state)
* `update` - (Defaults to 20 mins) Used when updating the volume when necessary during update - e.g. when resizing the volume
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ require (
github.com/hashicorp/terraform-plugin-mux v0.13.0
github.com/hashicorp/terraform-plugin-sdk/v2 v2.31.0
github.com/hashicorp/terraform-plugin-testing v1.6.0
github.com/linode/linodego v1.26.0
github.com/linode/linodego v1.27.0
github.com/linode/linodego/k8s v1.25.2
github.com/stretchr/testify v1.8.4
golang.org/x/crypto v0.17.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/linode/linodego v1.26.0 h1:2tOZ3Wxn4YvGBRgZi3Vz6dab+L16XUntJ9sJxh3ZBio=
github.com/linode/linodego v1.26.0/go.mod h1:kD7Bf1piWg/AXb9TA0ThAVwzR+GPf6r2PvbTbVk7PMA=
github.com/linode/linodego v1.27.0 h1:ISRsasemj/bG+jAxs1k6bTTeOJcp+Xpm810SY3GoI0k=
github.com/linode/linodego v1.27.0/go.mod h1:kD7Bf1piWg/AXb9TA0ThAVwzR+GPf6r2PvbTbVk7PMA=
github.com/linode/linodego/k8s v1.25.2 h1:PY6S0sAD3xANVvM9WY38bz9GqMTjIbytC8IJJ9Cv23o=
github.com/linode/linodego/k8s v1.25.2/go.mod h1:DC1XCSRZRGsmaa/ggpDPSDUmOM6aK1bhSIP6+f9Cwhc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
Expand Down
47 changes: 29 additions & 18 deletions linode/acceptance/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"math/rand"
"os"
"path/filepath"
"slices"
"sort"
"strconv"
"strings"
Expand All @@ -32,13 +33,15 @@ const (
SkipInstanceReadyPollKey = "skip_instance_ready_poll"

runLongTestsEnvVar = "RUN_LONG_TEST"
skipLongTestMessage = "This test has been marked as a long-running test and is skipped by default." +
skipLongTestMessage = "This test has been marked as a long-running test and is skipped by default. " +
"If you would like to run this test, please set the RUN_LONG_TEST environment variable to true."
)

type AttrValidateFunc func(val string) error

type ListAttrValidateFunc func(resourceName, path string, state *terraform.State) error
type (
AttrValidateFunc func(val string) error
ListAttrValidateFunc func(resourceName, path string, state *terraform.State) error
RegionFilterFunc func(v linodego.Region) bool
)

var (
optInTests map[string]struct{}
Expand Down Expand Up @@ -569,9 +572,7 @@ func ModifyProviderMeta(provider *schema.Provider, modifier ProviderMetaModifier
}

// GetRegionsWithCaps returns a list of regions that support the given capabilities.
func GetRegionsWithCaps(capabilities []string) ([]string, error) {
result := make([]string, 0)

func GetRegionsWithCaps(capabilities []string, filters ...RegionFilterFunc) ([]string, error) {
client, err := GetTestClient()
if err != nil {
return nil, err
Expand All @@ -582,36 +583,46 @@ func GetRegionsWithCaps(capabilities []string) ([]string, error) {
return nil, err
}

regionHasCaps := func(r linodego.Region) bool {
// Filter on capabilities
regionsWithCaps := slices.DeleteFunc(regions, func(region linodego.Region) bool {
capsMap := make(map[string]bool)

for _, c := range r.Capabilities {
for _, c := range region.Capabilities {
capsMap[strings.ToUpper(c)] = true
}

for _, c := range capabilities {
if _, ok := capsMap[strings.ToUpper(c)]; !ok {
return false
return true
}
}

return true
}
return false
})

for _, region := range regions {
if region.Status != "ok" || !regionHasCaps(region) {
continue
// Apply test-supplied filters
filteredRegions := slices.DeleteFunc(regionsWithCaps, func(region linodego.Region) bool {
for _, filter := range filters {
if !filter(region) {
return true
}
}

result = append(result, region.ID)
return false
})

result := make([]string, len(filteredRegions))

for i, r := range filteredRegions {
result[i] = r.ID
}

return result, nil
}

// GetRandomRegionWithCaps gets a random region given a list of region capabilities.
func GetRandomRegionWithCaps(capabilities []string) (string, error) {
regions, err := GetRegionsWithCaps(capabilities)
func GetRandomRegionWithCaps(capabilities []string, filters ...RegionFilterFunc) (string, error) {
regions, err := GetRegionsWithCaps(capabilities, filters...)
if err != nil {
return "", err
}
Expand Down
59 changes: 56 additions & 3 deletions linode/instance/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,7 @@ func changeInstanceType(
client *linodego.Client,
instanceID int,
targetType string,
migrationType linodego.InstanceMigrationType,
diskResize bool,
d *schema.ResourceData,
) (*linodego.Instance, error) {
Expand All @@ -748,11 +749,13 @@ func changeInstanceType(
resizeOpts := linodego.InstanceResizeOptions{
AllowAutoDiskResize: &diskResize,
Type: targetType,
MigrationType: migrationType,
}

tflog.Debug(ctx, "Resizing instance", map[string]any{
"body": resizeOpts,
"target_type": targetType,
"body": resizeOpts,
"target_type": targetType,
"migration_type": migrationType,
})

p, err := client.NewEventPoller(ctx, instance.ID, linodego.EntityLinode, linodego.ActionLinodeResize)
Expand Down Expand Up @@ -981,6 +984,10 @@ func applyInstanceTypeChange(
typ *linodego.LinodeType,
) (*linodego.Instance, error) {
resizeDisk := d.Get("resize_disk").(bool)
migrationType := linodego.InstanceMigrationType(
d.Get("migration_type").(string),
)

if resizeDisk {
// Verify that there are implicit disks defined
if d.GetRawConfig().GetAttr("image").IsNull() && d.GetRawConfig().GetAttr("disk").LengthInt() > 0 {
Expand All @@ -1005,7 +1012,53 @@ func applyInstanceTypeChange(
return nil, err
}

return changeInstanceType(ctx, client, instance.ID, typ.ID, resizeDisk, d)
return changeInstanceType(ctx, client, instance.ID, typ.ID, migrationType, resizeDisk, d)
}

// applyInstanceMigration synchronously migrates a Linode to a new region.
func applyInstanceMigration(
ctx context.Context,
d *schema.ResourceData,
client *linodego.Client,
instance *linodego.Instance,
targetRegion string,
) (*linodego.Instance, error) {
migrationType := linodego.InstanceMigrationType(
d.Get("migration_type").(string),
)

ctx = helper.SetLogFieldBulk(ctx, map[string]any{
"target_region": targetRegion,
"migration_type": migrationType,
})

tflog.Debug(ctx, "Migrating instance to new region")

p, err := client.NewEventPoller(ctx, instance.ID, linodego.EntityLinode, linodego.ActionLinodeMigrateDatacenter)
if err != nil {
return nil, fmt.Errorf("failed to initialize event poller %d: %s", instance.ID, err)
}

if err := client.MigrateInstance(ctx, instance.ID, linodego.InstanceMigrateOptions{
Region: targetRegion,
Type: migrationType,
}); err != nil {
return nil, fmt.Errorf("failed to migrate instance %d to region %s: %w", instance.ID, targetRegion, err)
}

_, err = p.WaitForFinished(ctx, getDeadlineSeconds(ctx, d))
if err != nil {
return nil, fmt.Errorf("failed to wait for instance %d to finish migration: %w", instance.ID, err)
}

tflog.Debug(ctx, "Instance migration has finished")

result, err := client.GetInstance(ctx, instance.ID)
if err != nil {
return nil, fmt.Errorf("failed to refresh instance %d: %w", instance.ID, err)
}

return result, nil
}

// detachConfigVolumes detaches any volumes associated with an InstanceConfig.Devices struct.
Expand Down
17 changes: 16 additions & 1 deletion linode/instance/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (

const (
LinodeInstanceCreateTimeout = 15 * time.Minute
LinodeInstanceUpdateTimeout = 25 * time.Minute
LinodeInstanceUpdateTimeout = time.Hour
LinodeInstanceDeleteTimeout = 10 * time.Minute
)

Expand Down Expand Up @@ -607,6 +607,21 @@ func updateResource(ctx context.Context, d *schema.ResourceData, meta interface{
rebootInstance = true
}

// If the region has changed,
// we should migrate the Linode.
if d.HasChange("region") {
instance, err = applyInstanceMigration(
ctx,
d,
&client,
instance,
d.Get("region").(string),
)
if err != nil {
return diag.Errorf("failed to migrate instance: %s", err)
}
}

oldSpec, newSpec, err := getInstanceTypeChange(ctx, d, &client)
if err != nil {
return diag.Errorf("Error getting resize info for instance: %s", err)
Expand Down