From c79e84c12cd552421956f7f3b9a1e7c596267846 Mon Sep 17 00:00:00 2001 From: Aayush Rangwala Date: Wed, 30 Aug 2023 21:55:05 +0530 Subject: [PATCH] Upgrade metal device resource and data source to use timeouts (#364) Provides support for timeouts in Create, Update and Delete for Metal Device as already mentioned in the documents #357 --- equinix/data_source_metal_device.go | 5 +- equinix/helpers_device.go | 66 +------- equinix/helpers_device_test.go | 3 +- equinix/provider.go | 10 ++ equinix/provider_test.go | 12 +- equinix/resource_metal_device.go | 73 +++++--- equinix/resource_metal_device_acc_test.go | 196 ++++++++++++++++++---- go.mod | 2 +- 8 files changed, 250 insertions(+), 117 deletions(-) diff --git a/equinix/data_source_metal_device.go b/equinix/data_source_metal_device.go index d0a9bc84f..f656068a1 100644 --- a/equinix/data_source_metal_device.go +++ b/equinix/data_source_metal_device.go @@ -1,6 +1,7 @@ package equinix import ( + "context" "encoding/json" "fmt" "path" @@ -14,7 +15,7 @@ import ( func dataSourceMetalDevice() *schema.Resource { return &schema.Resource{ - Read: dataSourceMetalDeviceRead, + ReadWithoutTimeout: diagnosticsWrapper(dataSourceMetalDeviceRead), Schema: map[string]*schema.Schema{ "hostname": { Type: schema.TypeString, @@ -201,7 +202,7 @@ func dataSourceMetalDevice() *schema.Resource { } } -func dataSourceMetalDeviceRead(d *schema.ResourceData, meta interface{}) error { +func dataSourceMetalDeviceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) error { client := meta.(*Config).metal hostnameRaw, hostnameOK := d.GetOk("hostname") diff --git a/equinix/helpers_device.go b/equinix/helpers_device.go index 427464c2f..6deef124b 100644 --- a/equinix/helpers_device.go +++ b/equinix/helpers_device.go @@ -1,6 +1,8 @@ package equinix import ( + "context" + "errors" "fmt" "log" "strings" @@ -8,7 +10,6 @@ import ( "time" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/packethost/packngo" @@ -142,7 +143,7 @@ func hwReservationStateRefreshFunc(client *packngo.Client, reservationId, instan } } -func waitUntilReservationProvisionable(client *packngo.Client, reservationId, instanceId string, delay, timeout, minTimeout time.Duration) error { +func waitUntilReservationProvisionable(ctx context.Context, client *packngo.Client, reservationId, instanceId string, delay, timeout, minTimeout time.Duration) error { stateConf := &retry.StateChangeConf{ Pending: []string{deprovisioning}, Target: []string{provisionable, reprovisioned}, @@ -151,7 +152,7 @@ func waitUntilReservationProvisionable(client *packngo.Client, reservationId, in Delay: delay, MinTimeout: minTimeout, } - _, err := stateConf.WaitForState() + _, err := stateConf.WaitForStateContext(ctx) return err } @@ -166,7 +167,7 @@ func getWaitForDeviceLock(deviceID string) *sync.WaitGroup { return wg } -func waitForDeviceAttribute(d *schema.ResourceData, targets []string, pending []string, attribute string, meta interface{}) (string, error) { +func waitForDeviceAttribute(ctx context.Context, d *schema.ResourceData, stateConf *retry.StateChangeConf) (string, error) { wg := getWaitForDeviceLock(d.Id()) wg.Wait() @@ -180,34 +181,11 @@ func waitForDeviceAttribute(d *schema.ResourceData, targets []string, pending [] wgMutex.Unlock() }() - if attribute != "state" && attribute != "network_type" { - return "", fmt.Errorf("unsupported attr to wait for: %s", attribute) + if stateConf == nil || stateConf.Refresh == nil { + return "", errors.New("invalid stateconf to wait for") } - stateConf := &retry.StateChangeConf{ - Pending: pending, - Target: targets, - Refresh: func() (interface{}, string, error) { - meta.(*Config).addModuleToMetalUserAgent(d) - client := meta.(*Config).metal - - device, _, err := client.Devices.Get(d.Id(), &packngo.GetOptions{Includes: []string{"project"}}) - if err == nil { - retAttrVal := device.State - if attribute == "network_type" { - networkType := device.GetNetworkType() - retAttrVal = networkType - } - return retAttrVal, retAttrVal, nil - } - return "error", "error", err - }, - Timeout: 60 * time.Minute, - Delay: 10 * time.Second, - MinTimeout: 3 * time.Second, - } - - attrValRaw, err := stateConf.WaitForState() + attrValRaw, err := stateConf.WaitForStateContext(ctx) if v, ok := attrValRaw.(string); ok { return v, err @@ -216,34 +194,6 @@ func waitForDeviceAttribute(d *schema.ResourceData, targets []string, pending [] return "", err } -// powerOnAndWait Powers on the device and waits for it to be active. -func powerOnAndWait(d *schema.ResourceData, meta interface{}) error { - meta.(*Config).addModuleToMetalUserAgent(d) - client := meta.(*Config).metal - - _, err := client.Devices.PowerOn(d.Id()) - if err != nil { - return friendlyError(err) - } - - _, err = waitForDeviceAttribute(d, []string{"active", "failed"}, []string{"off"}, "state", client) - if err != nil { - return err - } - state := d.Get("state").(string) - if state != "active" { - return friendlyError(fmt.Errorf("device in non-active state \"%s\"", state)) - } - return nil -} - -func validateFacilityForDevice(v interface{}, k string) (ws []string, errors []error) { - if v.(string) == "any" { - errors = append(errors, fmt.Errorf(`cannot use facility: "any"`)) - } - return -} - func ipAddressSchema() *schema.Resource { return &schema.Resource{ Schema: map[string]*schema.Schema{ diff --git a/equinix/helpers_device_test.go b/equinix/helpers_device_test.go index 7e01d8b46..f57c714d3 100644 --- a/equinix/helpers_device_test.go +++ b/equinix/helpers_device_test.go @@ -1,6 +1,7 @@ package equinix import ( + "context" "fmt" "testing" "time" @@ -149,7 +150,7 @@ func Test_waitUntilReservationProvisionable(t *testing.T) { // timeout * number of tests that reach timeout must be less than 30s (default go test timeout). for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := waitUntilReservationProvisionable(tt.args.meta, tt.args.reservationId, tt.args.instanceId, 50*time.Millisecond, 1*time.Second, 50*time.Millisecond); (err != nil) != tt.wantErr { + if err := waitUntilReservationProvisionable(context.Background(), tt.args.meta, tt.args.reservationId, tt.args.instanceId, 50*time.Millisecond, 1*time.Second, 50*time.Millisecond); (err != nil) != tt.wantErr { t.Errorf("waitUntilReservationProvisionable() error = %v, wantErr %v", err, tt.wantErr) } }) diff --git a/equinix/provider.go b/equinix/provider.go index 347a54eb5..63db867d3 100644 --- a/equinix/provider.go +++ b/equinix/provider.go @@ -448,3 +448,13 @@ func schemaSetToMap(set *schema.Set) map[int]interface{} { } return transformed } + +func diagnosticsWrapper(fn func(ctx context.Context, d *schema.ResourceData, meta interface{}) error) func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + if err := fn(ctx, d, meta); err != nil { + return diag.FromErr(err) + } + + return nil + } +} diff --git a/equinix/provider_test.go b/equinix/provider_test.go index 567ca8a75..27f650dcb 100644 --- a/equinix/provider_test.go +++ b/equinix/provider_test.go @@ -18,9 +18,10 @@ import ( ) var ( - testAccProviders map[string]*schema.Provider - testAccProvider *schema.Provider - testExternalProviders map[string]resource.ExternalProvider + testAccProviders map[string]*schema.Provider + testAccProviderFactories map[string]func() (*schema.Provider, error) + testAccProvider *schema.Provider + testExternalProviders map[string]resource.ExternalProvider ) type mockedResourceDataProvider struct { @@ -127,6 +128,11 @@ func init() { testAccProviders = map[string]*schema.Provider{ "equinix": testAccProvider, } + testAccProviderFactories = map[string]func() (*schema.Provider, error){ + "equinix": func() (*schema.Provider, error) { + return testAccProvider, nil + }, + } testExternalProviders = map[string]resource.ExternalProvider{ "random": { Source: "hashicorp/random", diff --git a/equinix/resource_metal_device.go b/equinix/resource_metal_device.go index 16da4298d..1596ff188 100644 --- a/equinix/resource_metal_device.go +++ b/equinix/resource_metal_device.go @@ -12,8 +12,8 @@ import ( "sort" "time" - "github.com/hashicorp/errwrap" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" @@ -37,12 +37,12 @@ func resourceMetalDevice() *schema.Resource { Update: schema.DefaultTimeout(30 * time.Minute), Delete: schema.DefaultTimeout(30 * time.Minute), }, - Create: resourceMetalDeviceCreate, - Read: resourceMetalDeviceRead, - Update: resourceMetalDeviceUpdate, - Delete: resourceMetalDeviceDelete, + CreateContext: diagnosticsWrapper(resourceMetalDeviceCreate), + ReadWithoutTimeout: diagnosticsWrapper(resourceMetalDeviceRead), + UpdateContext: diagnosticsWrapper(resourceMetalDeviceUpdate), + DeleteContext: diagnosticsWrapper(resourceMetalDeviceDelete), Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, + StateContext: schema.ImportStatePassthroughContext, }, Schema: map[string]*schema.Schema{ "project_id": { @@ -468,7 +468,7 @@ func reinstallDisabledAndNoChangesAllowed(attribute string) customdiff.ResourceC } } -func resourceMetalDeviceCreate(d *schema.ResourceData, meta interface{}) error { +func resourceMetalDeviceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) error { meta.(*Config).addModuleToMetalUserAgent(d) client := meta.(*Config).metal @@ -492,7 +492,7 @@ func resourceMetalDeviceCreate(d *schema.ResourceData, meta interface{}) error { metroRaw, metroOk := d.GetOk("metro") if !facsOk && !metroOk { - return friendlyError(errors.New("one of facilies and metro must be configured")) + return errors.New("one of facilies and metro must be configured") } if facsOk { @@ -575,16 +575,17 @@ func resourceMetalDeviceCreate(d *schema.ResourceData, meta interface{}) error { if attr, ok := d.GetOk("storage"); ok { s, err := structure.NormalizeJsonString(attr.(string)) if err != nil { - return errwrap.Wrapf("storage param contains invalid JSON: {{err}}", err) + return fmt.Errorf("storage param contains invalid JSON: %s", err) } var cpr packngo.CPR err = json.Unmarshal([]byte(s), &cpr) if err != nil { - return errwrap.Wrapf("Error parsing Storage string: {{err}}", err) + return fmt.Errorf("error parsing Storage string: %s", err) } createRequest.Storage = &cpr } + start := time.Now() newDevice, _, err := client.Devices.Create(createRequest) if err != nil { retErr := friendlyError(err) @@ -596,14 +597,15 @@ func resourceMetalDeviceCreate(d *schema.ResourceData, meta interface{}) error { d.SetId(newDevice.ID) - if err = waitForActiveDevice(d, meta); err != nil { + createTimeout := d.Timeout(schema.TimeoutCreate) - 30*time.Second - time.Since(start) + if err = waitForActiveDevice(ctx, d, meta, createTimeout); err != nil { return err } - return resourceMetalDeviceRead(d, meta) + return resourceMetalDeviceRead(ctx, d, meta) } -func resourceMetalDeviceRead(d *schema.ResourceData, meta interface{}) error { +func resourceMetalDeviceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) error { meta.(*Config).addModuleToMetalUserAgent(d) client := meta.(*Config).metal @@ -706,7 +708,7 @@ func resourceMetalDeviceRead(d *schema.ResourceData, meta interface{}) error { return nil } -func resourceMetalDeviceUpdate(d *schema.ResourceData, meta interface{}) error { +func resourceMetalDeviceUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) error { meta.(*Config).addModuleToMetalUserAgent(d) client := meta.(*Config).metal @@ -761,20 +763,22 @@ func resourceMetalDeviceUpdate(d *schema.ResourceData, meta interface{}) error { dPXE := d.Get("always_pxe").(bool) ur.AlwaysPXE = &dPXE } + + start := time.Now() if !reflect.DeepEqual(ur, packngo.DeviceUpdateRequest{}) { if _, _, err := client.Devices.Update(d.Id(), &ur); err != nil { return friendlyError(err) } } - if err := doReinstall(client, d, meta); err != nil { + if err := doReinstall(ctx, client, d, meta, start); err != nil { return err } - return resourceMetalDeviceRead(d, meta) + return resourceMetalDeviceRead(ctx, d, meta) } -func doReinstall(client *packngo.Client, d *schema.ResourceData, meta interface{}) error { +func doReinstall(ctx context.Context, client *packngo.Client, d *schema.ResourceData, meta interface{}, start time.Time) error { if d.HasChange("operating_system") || d.HasChange("user_data") || d.HasChange("custom_data") { reinstall, ok := d.GetOk("reinstall") @@ -802,7 +806,8 @@ func doReinstall(client *packngo.Client, d *schema.ResourceData, meta interface{ return friendlyError(err) } - if err := waitForActiveDevice(d, meta); err != nil { + updateTimeout := d.Timeout(schema.TimeoutUpdate) - 30*time.Second - time.Since(start) + if err := waitForActiveDevice(ctx, d, meta, updateTimeout); err != nil { return err } } @@ -810,7 +815,7 @@ func doReinstall(client *packngo.Client, d *schema.ResourceData, meta interface{ return nil } -func resourceMetalDeviceDelete(d *schema.ResourceData, meta interface{}) error { +func resourceMetalDeviceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) error { meta.(*Config).addModuleToMetalUserAgent(d) client := meta.(*Config).metal @@ -832,9 +837,9 @@ func resourceMetalDeviceDelete(d *schema.ResourceData, meta interface{}) error { wfrd, wfrdOK := d.GetOk("wait_for_reservation_deprovision") if wfrdOK && wfrd.(bool) { // avoid "context: deadline exceeded" - timeout := d.Timeout(schema.TimeoutDelete) - time.Minute - time.Since(start) + timeout := d.Timeout(schema.TimeoutDelete) - 30*time.Second - time.Since(start) - err := waitUntilReservationProvisionable(client, resId.(string), d.Id(), 10*time.Second, timeout, 3*time.Second) + err := waitUntilReservationProvisionable(ctx, client, resId.(string), d.Id(), 10*time.Second, timeout, 3*time.Second) if err != nil { return err } @@ -843,9 +848,31 @@ func resourceMetalDeviceDelete(d *schema.ResourceData, meta interface{}) error { return nil } -func waitForActiveDevice(d *schema.ResourceData, meta interface{}) error { +func waitForActiveDevice(ctx context.Context, d *schema.ResourceData, meta interface{}, timeout time.Duration) error { + targets := []string{"active", "failed"} + pending := []string{"queued", "provisioning", "reinstalling"} + + stateConf := &retry.StateChangeConf{ + Pending: pending, + Target: targets, + Refresh: func() (interface{}, string, error) { + meta.(*Config).addModuleToMetalUserAgent(d) + client := meta.(*Config).metal + + device, _, err := client.Devices.Get(d.Id(), &packngo.GetOptions{Includes: []string{"project"}}) + if err == nil { + retAttrVal := device.State + return retAttrVal, retAttrVal, nil + } + return "error", "error", err + }, + Timeout: timeout, + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + // Wait for the device so we can get the networking attributes that show up after a while. - state, err := waitForDeviceAttribute(d, []string{"active", "failed"}, []string{"queued", "provisioning", "reinstalling"}, "state", meta) + state, err := waitForDeviceAttribute(ctx, d, stateConf) if err != nil { d.SetId("") fErr := friendlyError(err) diff --git a/equinix/resource_metal_device_acc_test.go b/equinix/resource_metal_device_acc_test.go index ebf892158..8f4d31c21 100644 --- a/equinix/resource_metal_device_acc_test.go +++ b/equinix/resource_metal_device_acc_test.go @@ -1,6 +1,7 @@ package equinix import ( + "context" "fmt" "log" "net" @@ -74,8 +75,9 @@ func testSweepDevices(region string) error { // Regexp vars for use with resource.ExpectError var ( - matchErrMustBeProvided = regexp.MustCompile(".* must be provided when .*") - matchErrShouldNotBeAnIPXE = regexp.MustCompile(`.*"user_data" should not be an iPXE.*`) + matchErrMustBeProvided = regexp.MustCompile(".* must be provided when .*") + matchErrShouldNotBeAnIPXE = regexp.MustCompile(`.*"user_data" should not be an iPXE.*`) + matchErrDeviceReadyTimeout = regexp.MustCompile(".* timeout while waiting for state to become 'active, failed'.*") ) // This function should be used to find available plans in all test where a metal_device resource is needed. @@ -747,30 +749,6 @@ resource "equinix_metal_device" "test" { `, confAccMetalDevice_base(preferable_plans, preferable_metros, preferable_os), projSuffix, rInt, rInt, rInt, testDeviceTerminationTime()) } -func testAccMetalDeviceConfig_varname_pxe(rInt int, projSuffix string) string { - return fmt.Sprintf(` -%s - -resource "equinix_metal_project" "test" { - name = "tfacc-device-%s" -} - -resource "equinix_metal_device" "test" { - hostname = "tfacc-test-device-%d" - description = "test-desc-%d" - plan = local.plan - metro = local.metro - operating_system = local.os - billing_cycle = "hourly" - project_id = "${equinix_metal_project.test.id}" - tags = ["%d"] - always_pxe = true - ipxe_script_url = "http://matchbox.foo.wtf:8080/boot.ipxe" - termination_time = "%s" -} -`, confAccMetalDevice_base(preferable_plans, preferable_metros, preferable_os), projSuffix, rInt, rInt, rInt, testDeviceTerminationTime()) -} - func testAccMetalDeviceConfig_metro(projSuffix string) string { return fmt.Sprintf(` %s @@ -938,6 +916,76 @@ resource "equinix_metal_device" "test_ipxe_missing" { always_pxe = true }` +func testAccMetalDeviceConfig_timeout(projSuffix, createTimeout, updateTimeout, deleteTimeout string) string { + if createTimeout == "" { + createTimeout = "20m" + } + if updateTimeout == "" { + updateTimeout = "20m" + } + if deleteTimeout == "" { + deleteTimeout = "20m" + } + + return fmt.Sprintf(` +%s + +resource "equinix_metal_project" "test" { + name = "tfacc-device-%s" +} + +resource "equinix_metal_device" "test" { + hostname = "tfacc-test-device" + plan = local.plan + metro = local.metro + operating_system = local.os + billing_cycle = "hourly" + project_id = "${equinix_metal_project.test.id}" + termination_time = "%s" + + timeouts { + create = "%s" + update = "%s" + delete = "%s" + } +} +`, confAccMetalDevice_base(preferable_plans, preferable_metros, preferable_os), projSuffix, testDeviceTerminationTime(), createTimeout, updateTimeout, deleteTimeout) +} + +func testAccMetalDeviceConfig_reinstall_timeout(projSuffix, updateTimeout string) string { + if updateTimeout == "" { + updateTimeout = "20m" + } + + return fmt.Sprintf(` +%s + +resource "equinix_metal_project" "test" { + name = "tfacc-device-%s" +} + +resource "equinix_metal_device" "test" { + hostname = "tfacc-test-device" + plan = local.plan + metro = local.metro + operating_system = local.os + billing_cycle = "hourly" + project_id = "${equinix_metal_project.test.id}" + user_data = "#!/usr/bin/env sh\necho Reinstall\n" + termination_time = "%s" + + reinstall { + enabled = true + deprovision_fast = true + } + + timeouts { + update = "%s" + } +} +`, confAccMetalDevice_base(preferable_plans, preferable_metros, preferable_os), projSuffix, testDeviceTerminationTime(), updateTimeout) +} + type mockDeviceService struct { GetFn func(deviceID string, opts *packngo.GetOptions) (*packngo.Device, *packngo.Response, error) } @@ -950,7 +998,7 @@ func (m *mockDeviceService) Create(device *packngo.DeviceCreateRequest) (*packng return nil, nil, mockFuncNotImplemented("Create") } -func (m *mockDeviceService) Delete(string, bool) (*packngo.Response, error) { +func (m *mockDeviceService) Delete(deviceId string, forceDetachVolume bool) (*packngo.Response, error) { return nil, mockFuncNotImplemented("Delete") } @@ -958,7 +1006,7 @@ func (m *mockDeviceService) List(string, *packngo.ListOptions) ([]packngo.Device return nil, nil, mockFuncNotImplemented("List") } -func (m *mockDeviceService) Update(string, *packngo.DeviceUpdateRequest) (*packngo.Device, *packngo.Response, error) { +func (m *mockDeviceService) Update(deviceId string, updateReq *packngo.DeviceUpdateRequest) (*packngo.Device, *packngo.Response, error) { return nil, nil, mockFuncNotImplemented("Update") } @@ -1121,9 +1169,99 @@ func TestAccMetalDevice_readErrorHandling(t *testing.T) { if tt.args.newResource { d.MarkNewResource() } - if err := resourceMetalDeviceRead(d, tt.args.meta); (err != nil) != tt.wantErr { + if err := resourceMetalDeviceRead(context.Background(), d, tt.args.meta); (err != nil) != tt.wantErr { t.Errorf("resourceMetalDeviceRead() error = %v, wantErr %v", err, tt.wantErr) } }) } } + +func testAccWaitForMetalDeviceActive(project, deviceHostName string) resource.ImportStateIdFunc { + return func(state *terraform.State) (string, error) { + defaultTimeout := 20 * time.Minute + + rs, ok := state.RootModule().Resources[project] + if !ok { + return "", fmt.Errorf("Project Not found in the state: %s", project) + } + if rs.Primary.ID == "" { + return "", fmt.Errorf("No Record ID is set") + } + + meta := testAccProvider.Meta() + rd := new(schema.ResourceData) + meta.(*Config).addModuleToMetalUserAgent(rd) + client := meta.(*Config).metal + devices, _, err := client.Devices.List(rs.Primary.ID, &packngo.ListOptions{Search: deviceHostName}) + if err != nil { + return "", fmt.Errorf("error while fetching devices for project [%s], error: %w", rs.Primary.ID, err) + } + if len(devices) == 0 { + return "", fmt.Errorf("Not able to find devices in project [%s]", rs.Primary.ID) + } + if len(devices) > 1 { + return "", fmt.Errorf("Found more than one device with the hostname in project [%s]", rs.Primary.ID) + } + + rd.SetId(devices[0].ID) + return devices[0].ID, waitForActiveDevice(context.Background(), rd, testAccProvider.Meta(), defaultTimeout) + } +} + +func TestAccMetalDeviceCreate_timeout(t *testing.T) { + rs := acctest.RandString(10) + r := "equinix_metal_device.test" + hostname := "tfacc-test-device" + project := "equinix_metal_project.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ExternalProviders: testExternalProviders, + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccMetalDeviceCheckDestroyed, + Steps: []resource.TestStep{ + { + Config: testAccMetalDeviceConfig_timeout(rs, "10s", "", ""), + ExpectError: matchErrDeviceReadyTimeout, + }, + { + /** + Step 1 errors out, state doesnt have device, need to import that in the state before deleting + */ + ResourceName: r, + ImportState: true, + ImportStateIdFunc: testAccWaitForMetalDeviceActive(project, hostname), + ImportStatePersist: true, + }, + { + Config: testAccMetalDeviceConfig_timeout(rs, "", "", ""), + Destroy: true, + }, + }, + }) +} + +func TestAccMetalDeviceUpdate_timeout(t *testing.T) { + var d1 packngo.Device + rs := acctest.RandString(10) + r := "equinix_metal_device.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ExternalProviders: testExternalProviders, + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccMetalDeviceCheckDestroyed, + Steps: []resource.TestStep{ + { + Config: testAccMetalDeviceConfig_timeout(rs, "", "", ""), + Check: resource.ComposeTestCheckFunc( + testAccMetalDeviceExists(r, &d1), + ), + }, + { + Config: testAccMetalDeviceConfig_reinstall_timeout(rs, "10s"), + ExpectError: matchErrDeviceReadyTimeout, + }, + }, + }) +} diff --git a/go.mod b/go.mod index f908be4f8..956cb970e 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,6 @@ require ( github.com/equinix/oauth2-go v1.0.0 github.com/equinix/rest-go v1.3.0 github.com/gruntwork-io/terratest v0.43.0 - github.com/hashicorp/errwrap v1.1.0 github.com/hashicorp/go-cty v1.4.1-0.20200723130312-85980079f637 github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-retryablehttp v0.7.4 @@ -48,6 +47,7 @@ require ( github.com/google/uuid v1.3.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect github.com/googleapis/gax-go/v2 v2.11.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-checkpoint v0.5.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-getter v1.7.1 // indirect