From 72d7f2b30c6d02374afb34d9ea47769d52553a29 Mon Sep 17 00:00:00 2001 From: Ayush Rangwala Date: Mon, 25 Sep 2023 23:33:45 +0530 Subject: [PATCH] Suport timeouts in metal Spot Requests --- .../data_source_metal_spot_market_request.go | 5 +- equinix/resource_metal_spot_market_request.go | 52 ++++-------- ...urce_metal_spot_market_request_acc_test.go | 79 +++++++++++++++++-- go.mod | 1 + go.sum | 1 + 5 files changed, 92 insertions(+), 46 deletions(-) diff --git a/equinix/data_source_metal_spot_market_request.go b/equinix/data_source_metal_spot_market_request.go index 6527f3eee..7b8f94cea 100644 --- a/equinix/data_source_metal_spot_market_request.go +++ b/equinix/data_source_metal_spot_market_request.go @@ -1,6 +1,7 @@ package equinix import ( + "context" "sort" "strings" "time" @@ -11,7 +12,7 @@ import ( func dataSourceMetalSpotMarketRequest() *schema.Resource { return &schema.Resource{ - Read: dataSourceMetalSpotMarketRequestRead, + ReadContext: diagnosticsWrapper(dataSourceMetalSpotMarketRequestRead), Schema: map[string]*schema.Schema{ "request_id": { @@ -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) diff --git a/equinix/resource_metal_spot_market_request.go b/equinix/resource_metal_spot_market_request.go index 9c33ea54e..2052ce43d 100644 --- a/equinix/resource_metal_spot_market_request.go +++ b/equinix/resource_metal_spot_market_request.go @@ -1,6 +1,7 @@ package equinix import ( + "context" "fmt" "log" "reflect" @@ -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": { @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 } @@ -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) diff --git a/equinix/resource_metal_spot_market_request_acc_test.go b/equinix/resource_metal_spot_market_request_acc_test.go index f3750a25c..7291f401a 100644 --- a/equinix/resource_metal_spot_market_request_acc_test.go +++ b/equinix/resource_metal_spot_market_request_acc_test.go @@ -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) { @@ -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{ @@ -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{ @@ -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, + }, + }, + }) +} diff --git a/go.mod b/go.mod index 0ce4605c9..ae176069d 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 21478e5b5..5f9dcc7fb 100644 --- a/go.sum +++ b/go.sum @@ -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=