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

fix: Timeout for metal spot market requests #379

Merged
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading