Skip to content

Commit

Permalink
Upgrade metal device resource and data source to use timeouts (equini…
Browse files Browse the repository at this point in the history
…x#364)

Provides support for timeouts in Create, Update and Delete for Metal
Device as already mentioned in the documents
  • Loading branch information
aayushrangwala committed Sep 7, 2023
1 parent c246602 commit f533073
Show file tree
Hide file tree
Showing 7 changed files with 263 additions and 130 deletions.
5 changes: 3 additions & 2 deletions equinix/data_source_metal_device.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package equinix

import (
"context"
"encoding/json"
"fmt"
"path"
Expand All @@ -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,
Expand Down Expand Up @@ -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")
Expand Down
65 changes: 8 additions & 57 deletions equinix/helpers_device.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package equinix

import (
"context"
"errors"
"fmt"
"log"
"strings"
Expand Down Expand Up @@ -141,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},
Expand All @@ -150,7 +152,7 @@ func waitUntilReservationProvisionable(client *packngo.Client, reservationId, in
Delay: delay,
MinTimeout: minTimeout,
}
_, err := stateConf.WaitForState()
_, err := stateConf.WaitForStateContext(ctx)
return err
}

Expand All @@ -165,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()

Expand All @@ -179,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
Expand All @@ -215,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{
Expand Down
3 changes: 2 additions & 1 deletion equinix/helpers_device_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package equinix

import (
"context"
"fmt"
"testing"
"time"
Expand Down Expand Up @@ -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)
}
})
Expand Down
10 changes: 10 additions & 0 deletions equinix/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
12 changes: 9 additions & 3 deletions equinix/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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",
Expand Down
Loading

0 comments on commit f533073

Please sign in to comment.