Skip to content

Commit

Permalink
fix: Timeout for metal spot market requests (#379)
Browse files Browse the repository at this point in the history
This PR will fix the timeout support for metal spot market requests
which is already documented publicly

relates to:
#357
  • Loading branch information
aayushrangwala authored Sep 26, 2023
1 parent 45e8380 commit 352a76c
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 46 deletions.
5 changes: 3 additions & 2 deletions equinix/data_source_metal_spot_market_request.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package equinix

import (
"context"
"sort"
"strings"
"time"
Expand All @@ -11,7 +12,7 @@ import (

func dataSourceMetalSpotMarketRequest() *schema.Resource {
return &schema.Resource{
Read: dataSourceMetalSpotMarketRequestRead,
ReadContext: diagnosticsWrapper(dataSourceMetalSpotMarketRequestRead),

Schema: map[string]*schema.Schema{
"request_id": {
Expand Down Expand Up @@ -72,7 +73,7 @@ func dataSourceMetalSpotMarketRequest() *schema.Resource {
}
}

func dataSourceMetalSpotMarketRequestRead(d *schema.ResourceData, meta interface{}) error {
func dataSourceMetalSpotMarketRequestRead(ctx context.Context, d *schema.ResourceData, meta interface{}) error {
client := meta.(*Config).metal
id := d.Get("request_id").(string)

Expand Down
52 changes: 14 additions & 38 deletions equinix/resource_metal_spot_market_request.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package equinix

import (
"context"
"fmt"
"log"
"reflect"
Expand All @@ -15,11 +16,11 @@ import (

func resourceMetalSpotMarketRequest() *schema.Resource {
return &schema.Resource{
Create: resourceMetalSpotMarketRequestCreate,
Read: resourceMetalSpotMarketRequestRead,
Delete: resourceMetalSpotMarketRequestDelete,
CreateContext: diagnosticsWrapper(resourceMetalSpotMarketRequestCreate),
ReadContext: diagnosticsWrapper(resourceMetalSpotMarketRequestRead),
DeleteContext: diagnosticsWrapper(resourceMetalSpotMarketRequestDelete),
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
"devices_min": {
Expand Down Expand Up @@ -211,7 +212,7 @@ func resourceMetalSpotMarketRequest() *schema.Resource {
}
}

func resourceMetalSpotMarketRequestCreate(d *schema.ResourceData, meta interface{}) error {
func resourceMetalSpotMarketRequestCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) error {
meta.(*Config).addModuleToMetalUserAgent(d)
client := meta.(*Config).metal
var waitForDevices bool
Expand Down Expand Up @@ -326,6 +327,7 @@ func resourceMetalSpotMarketRequestCreate(d *schema.ResourceData, meta interface
Parameters: params,
}

start := time.Now()
smr, _, err := client.SpotMarketRequests.Create(smrc, d.Get("project_id").(string))
if err != nil {
return err
Expand All @@ -338,22 +340,22 @@ func resourceMetalSpotMarketRequestCreate(d *schema.ResourceData, meta interface
Pending: []string{"not_done"},
Target: []string{"done"},
Refresh: resourceStateRefreshFunc(d, meta),
Timeout: d.Timeout(schema.TimeoutCreate),
Timeout: d.Timeout(schema.TimeoutCreate) - time.Since(start) - time.Second*10, // reduce 30s to avoid context deadline
MinTimeout: 5 * time.Second,
Delay: 3 * time.Second, // Wait 10 secs before starting
NotFoundChecks: 600, // Setting high number, to support long timeouts
}

_, err = stateConf.WaitForState()
_, err = stateConf.WaitForStateContext(ctx)
if err != nil {
return err
}
}

return resourceMetalSpotMarketRequestRead(d, meta)
return resourceMetalSpotMarketRequestRead(ctx, d, meta)
}

func resourceMetalSpotMarketRequestRead(d *schema.ResourceData, meta interface{}) error {
func resourceMetalSpotMarketRequestRead(ctx context.Context, d *schema.ResourceData, meta interface{}) error {
meta.(*Config).addModuleToMetalUserAgent(d)
client := meta.(*Config).metal

Expand Down Expand Up @@ -393,7 +395,7 @@ func resourceMetalSpotMarketRequestRead(d *schema.ResourceData, meta interface{}
})
}

func resourceMetalSpotMarketRequestDelete(d *schema.ResourceData, meta interface{}) error {
func resourceMetalSpotMarketRequestDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) error {
meta.(*Config).addModuleToMetalUserAgent(d)
client := meta.(*Config).metal
var waitForDevices bool
Expand All @@ -411,13 +413,13 @@ func resourceMetalSpotMarketRequestDelete(d *schema.ResourceData, meta interface
Pending: []string{"not_done"},
Target: []string{"done"},
Refresh: resourceStateRefreshFunc(d, meta),
Timeout: d.Timeout(schema.TimeoutDelete),
Timeout: d.Timeout(schema.TimeoutDelete) - 30*time.Second,
MinTimeout: 5 * time.Second,
Delay: 3 * time.Second, // Wait 10 secs before starting
NotFoundChecks: 600, // Setting high number, to support long timeouts
}

_, err = stateConf.WaitForState()
_, err = stateConf.WaitForStateContext(ctx)
if err != nil {
return err
}
Expand All @@ -433,32 +435,6 @@ func resourceMetalSpotMarketRequestDelete(d *schema.ResourceData, meta interface
return ignoreResponseErrors(httpForbidden, httpNotFound)(resp, err)
}

type InstanceParams []map[string]interface{}

func getInstanceParams(params *packngo.SpotMarketRequestInstanceParameters) InstanceParams {
p := InstanceParams{{
"billing_cycle": params.BillingCycle,
"plan": params.Plan,
"operating_system": params.OperatingSystem,
"hostname": params.Hostname,
"always_pxe": params.AlwaysPXE,
"description": params.Description,
"features": params.Features,
"locked": params.Locked,
"project_ssh_keys": params.ProjectSSHKeys,
"user_ssh_keys": params.UserSSHKeys,
"userdata": params.UserData,
"customdata": params.CustomData,
"ipxe_script_url": params.IPXEScriptURL,
"tags": params.Tags,
}}
if params.TerminationTime != nil {
p[0]["termintation_time"] = params.TerminationTime.Time
p[0]["termination_time"] = params.TerminationTime.Time
}
return p
}

func resourceStateRefreshFunc(d *schema.ResourceData, meta interface{}) retry.StateRefreshFunc {
return func() (interface{}, string, error) {
meta.(*Config).addModuleToMetalUserAgent(d)
Expand Down
79 changes: 73 additions & 6 deletions equinix/resource_metal_spot_market_request_acc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,17 @@ package equinix

import (
"fmt"
"regexp"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
"github.com/packethost/packngo"
"regexp"
"testing"
)

var (
matchErrOverbid = regexp.MustCompile(".* exceeds the maximum bid price .*")
matchErrTimeout = regexp.MustCompile(".* timeout while waiting for state to become 'done' .")
matchErrTimeout = regexp.MustCompile(".* timeout while waiting for state to become 'done'.*")
)

func TestAccMetalSpotMarketRequest_basic(t *testing.T) {
Expand All @@ -23,7 +22,7 @@ func TestAccMetalSpotMarketRequest_basic(t *testing.T) {
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ExternalProviders: testExternalProviders,
Providers: testAccProviders,
ProviderFactories: testAccProviderFactories,
CheckDestroy: testAccMetalSpotMarketRequestCheckDestroyed,
ErrorCheck: skipIfOverbidOrTimedOut(t),
Steps: []resource.TestStep{
Expand Down Expand Up @@ -151,7 +150,7 @@ func TestAccMetalSpotMarketRequest_Import(t *testing.T) {
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ExternalProviders: testExternalProviders,
Providers: testAccProviders,
ProviderFactories: testAccProviderFactories,
CheckDestroy: testAccMetalSpotMarketRequestCheckDestroyed,
ErrorCheck: skipIfOverbidOrTimedOut(t),
Steps: []resource.TestStep{
Expand Down Expand Up @@ -192,3 +191,71 @@ func skipIfOverbidOrTimedOut(t *testing.T) resource.ErrorCheckFunc {
return err
}
}

func testAccMetalSpotMarketRequestConfig_timeout(projSuffix, createTimeout string) string {
if createTimeout == "" {
createTimeout = "30m"
}

return fmt.Sprintf(`
%s
resource "equinix_metal_project" "test" {
name = "tfacc-spot_market_request-%s"
}
data "equinix_metal_spot_market_price" "test" {
metro = local.metro
plan = local.plan
}
data "equinix_metal_spot_market_request" "dreq" {
request_id = equinix_metal_spot_market_request.request.id
timeouts {
create = "%s"
}
}
resource "equinix_metal_spot_market_request" "request" {
project_id = equinix_metal_project.test.id
max_bid_price = format("%%.2f", data.equinix_metal_spot_market_price.test.price)
metro = data.equinix_metal_spot_market_price.test.metro
devices_min = 1
devices_max = 1
wait_for_devices = true
instance_parameters {
hostname = "tfacc-testspot"
billing_cycle = "hourly"
operating_system = local.os
plan = local.plan
}
timeouts {
create = "%s"
}
}`, confAccMetalDevice_base(preferable_plans, preferable_metros, preferable_os), projSuffix, createTimeout, createTimeout)
}

func TestAccMetalSpotMarketRequestCreate_WithTimeout(t *testing.T) {
projSuffix := acctest.RandString(10)

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ExternalProviders: testExternalProviders,
ProviderFactories: testAccProviderFactories,
CheckDestroy: testAccMetalSpotMarketRequestCheckDestroyed,
ErrorCheck: func(err error) error {
if matchErrOverbid.MatchString(err.Error()) {
t.Skipf("price was higher than max allowed bid; skipping")
}
return err
},
Steps: []resource.TestStep{
{
Config: testAccMetalSpotMarketRequestConfig_timeout(projSuffix, "5s"),
ExpectError: matchErrTimeout,
},
},
})
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ require (
github.com/hashicorp/terraform-plugin-docs v0.14.1
github.com/hashicorp/terraform-plugin-sdk/v2 v2.26.1
github.com/packethost/packngo v0.30.0
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.8.4
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
golang.org/x/oauth2 v0.11.0
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,7 @@ github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQ
github.com/packethost/packngo v0.30.0 h1:JVeTwbXXETsLTDQncUbYwIFpkOp/xevXrffM2HrFECI=
github.com/packethost/packngo v0.30.0/go.mod h1:BT/XcdwLVmeMtGPbovnxCpnI1s9ylSE1cs/7pq007NE=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
Expand Down

0 comments on commit 352a76c

Please sign in to comment.