From 5fa78b96c16f4e8e983461851bc72fc78ca1b260 Mon Sep 17 00:00:00 2001 From: thogarty <139183873+thogarty@users.noreply.github.com> Date: Thu, 28 Mar 2024 17:27:48 -0700 Subject: [PATCH 1/2] fix: Use default timeouts for Fabric wait methods (#631) * All fabric resource wait methods have new argument for timeout * Using 30*time.Second to account for delay in wait methods * Timeout is for entire CRUD method so we subtract times as we go from the start of the method * Testing updated to account for changes NOTE: We have errors running Fabric tests in parallel for now. Not sure of the issue, but this change has been testing manually locally with each test being run in an isolated manner. All tests are successful. Logs are too large to post. --- ...ata_source_fabric_cloud_router_acc_test.go | 4 +- equinix/resource_fabric_cloud_router.go | 33 ++++++++----- .../resource_fabric_cloud_router_acc_test.go | 7 +-- equinix/resource_fabric_connection.go | 48 +++++++++++-------- .../resource_fabric_connection_acc_test.go | 42 +++++++++++----- equinix/resource_fabric_network.go | 34 ++++++++----- equinix/resource_fabric_network_acc_test.go | 3 +- equinix/resource_fabric_routing_protocol.go | 34 ++++++++----- ...source_fabric_routing_protocol_acc_test.go | 5 +- equinix/resource_fabric_service_profile.go | 30 +++++++----- ...esource_fabric_service_profile_acc_test.go | 3 +- 11 files changed, 151 insertions(+), 92 deletions(-) diff --git a/equinix/data_source_fabric_cloud_router_acc_test.go b/equinix/data_source_fabric_cloud_router_acc_test.go index be437389d..396727edc 100644 --- a/equinix/data_source_fabric_cloud_router_acc_test.go +++ b/equinix/data_source_fabric_cloud_router_acc_test.go @@ -24,7 +24,7 @@ func TestAccDataSourceFabricCloudRouter_PFCR(t *testing.T) { resource.TestCheckResourceAttr("data.equinix_fabric_cloud_router.example", "notifications.0.emails.0", "test@equinix.com"), resource.TestCheckResourceAttr("data.equinix_fabric_cloud_router.example", "order.0.purchase_order_number", "1-323292"), resource.TestCheckResourceAttr("data.equinix_fabric_cloud_router.example", "location.0.metro_code", "SV"), - resource.TestCheckResourceAttr("data.equinix_fabric_cloud_router.example", "package.0.code", "LAB"), + resource.TestCheckResourceAttr("data.equinix_fabric_cloud_router.example", "package.0.code", "STANDARD"), resource.TestCheckResourceAttrSet("data.equinix_fabric_cloud_router.example", "project.0.project_id"), resource.TestCheckResourceAttrSet("data.equinix_fabric_cloud_router.example", "account.0.account_number"), resource.TestCheckResourceAttrSet("data.equinix_fabric_cloud_router.example", "href"), @@ -62,7 +62,7 @@ func ConfigCreateCloudRouterResource_PFCR() string { metro_code= "SV" } package { - code="LAB" + code="STANDARD" } project { project_id = "291639000636552" diff --git a/equinix/resource_fabric_cloud_router.go b/equinix/resource_fabric_cloud_router.go index 66f75d77f..8d1c6b857 100644 --- a/equinix/resource_fabric_cloud_router.go +++ b/equinix/resource_fabric_cloud_router.go @@ -189,10 +189,10 @@ func fabricCloudRouterResourceSchema() map[string]*schema.Schema { func resourceFabricCloudRouter() *schema.Resource { return &schema.Resource{ Timeouts: &schema.ResourceTimeout{ - Create: schema.DefaultTimeout(6 * time.Minute), + Create: schema.DefaultTimeout(10 * time.Minute), Update: schema.DefaultTimeout(10 * time.Minute), - Delete: schema.DefaultTimeout(6 * time.Minute), - Read: schema.DefaultTimeout(6 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + Read: schema.DefaultTimeout(10 * time.Minute), }, ReadContext: resourceFabricCloudRouterRead, CreateContext: resourceFabricCloudRouterCreate, @@ -278,13 +278,15 @@ func resourceFabricCloudRouterCreate(ctx context.Context, d *schema.ResourceData createRequest.Order = &order } + start := time.Now() fcr, _, err := client.CloudRoutersApi.CreateCloudRouter(ctx, createRequest) if err != nil { return diag.FromErr(equinix_errors.FormatFabricError(err)) } d.SetId(fcr.Uuid) - if _, err = waitUntilCloudRouterIsProvisioned(d.Id(), meta, ctx); err != nil { + createTimeout := d.Timeout(schema.TimeoutCreate) - 30*time.Second - time.Since(start) + if _, err = waitUntilCloudRouterIsProvisioned(d.Id(), meta, ctx, createTimeout); err != nil { return diag.Errorf("error waiting for Cloud Router (%s) to be created: %s", d.Id(), err) } @@ -387,7 +389,9 @@ func getCloudRouterUpdateRequest(conn v4.CloudRouter, d *schema.ResourceData) (v func resourceFabricCloudRouterUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*config.Config).FabricClient ctx = context.WithValue(ctx, v4.ContextAccessToken, meta.(*config.Config).FabricAuthToken) - dbConn, err := waitUntilCloudRouterIsProvisioned(d.Id(), meta, ctx) + start := time.Now() + updateTimeout := d.Timeout(schema.TimeoutUpdate) - 30*time.Second - time.Since(start) + dbConn, err := waitUntilCloudRouterIsProvisioned(d.Id(), meta, ctx, updateTimeout) if err != nil { if !strings.Contains(err.Error(), "500") { d.SetId("") @@ -405,7 +409,8 @@ func resourceFabricCloudRouterUpdate(ctx context.Context, d *schema.ResourceData return diag.FromErr(equinix_errors.FormatFabricError(err)) } updateFg := v4.CloudRouter{} - updateFg, err = waitForCloudRouterUpdateCompletion(d.Id(), meta, ctx) + updateTimeout = d.Timeout(schema.TimeoutUpdate) - 30*time.Second - time.Since(start) + updateFg, err = waitForCloudRouterUpdateCompletion(d.Id(), meta, ctx, updateTimeout) if err != nil { if !strings.Contains(err.Error(), "500") { @@ -418,7 +423,7 @@ func resourceFabricCloudRouterUpdate(ctx context.Context, d *schema.ResourceData return setCloudRouterMap(d, updateFg) } -func waitForCloudRouterUpdateCompletion(uuid string, meta interface{}, ctx context.Context) (v4.CloudRouter, error) { +func waitForCloudRouterUpdateCompletion(uuid string, meta interface{}, ctx context.Context, timeout time.Duration) (v4.CloudRouter, error) { log.Printf("Waiting for Cloud Router update to complete, uuid %s", uuid) stateConf := &retry.StateChangeConf{ Target: []string{string(v4.PROVISIONED_CloudRouterAccessPointState)}, @@ -430,7 +435,7 @@ func waitForCloudRouterUpdateCompletion(uuid string, meta interface{}, ctx conte } return dbConn, string(*dbConn.State), nil }, - Timeout: 2 * time.Minute, + Timeout: timeout, Delay: 30 * time.Second, MinTimeout: 30 * time.Second, } @@ -444,7 +449,7 @@ func waitForCloudRouterUpdateCompletion(uuid string, meta interface{}, ctx conte return dbConn, err } -func waitUntilCloudRouterIsProvisioned(uuid string, meta interface{}, ctx context.Context) (v4.CloudRouter, error) { +func waitUntilCloudRouterIsProvisioned(uuid string, meta interface{}, ctx context.Context, timeout time.Duration) (v4.CloudRouter, error) { log.Printf("Waiting for Cloud Router to be provisioned, uuid %s", uuid) stateConf := &retry.StateChangeConf{ Pending: []string{ @@ -461,7 +466,7 @@ func waitUntilCloudRouterIsProvisioned(uuid string, meta interface{}, ctx contex } return dbConn, string(*dbConn.State), nil }, - Timeout: 5 * time.Minute, + Timeout: timeout, Delay: 30 * time.Second, MinTimeout: 30 * time.Second, } @@ -479,6 +484,7 @@ func resourceFabricCloudRouterDelete(ctx context.Context, d *schema.ResourceData diags := diag.Diagnostics{} client := meta.(*config.Config).FabricClient ctx = context.WithValue(ctx, v4.ContextAccessToken, meta.(*config.Config).FabricAuthToken) + start := time.Now() _, err := client.CloudRoutersApi.DeleteCloudRouterByUuid(ctx, d.Id()) if err != nil { errors, ok := err.(v4.GenericSwaggerError).Model().([]v4.ModelError) @@ -491,14 +497,15 @@ func resourceFabricCloudRouterDelete(ctx context.Context, d *schema.ResourceData return diag.FromErr(equinix_errors.FormatFabricError(err)) } - err = WaitUntilCloudRouterDeprovisioned(d.Id(), meta, ctx) + deleteTimeout := d.Timeout(schema.TimeoutDelete) - 30*time.Second - time.Since(start) + err = WaitUntilCloudRouterDeprovisioned(d.Id(), meta, ctx, deleteTimeout) if err != nil { return diag.FromErr(fmt.Errorf("API call failed while waiting for resource deletion. Error %v", err)) } return diags } -func WaitUntilCloudRouterDeprovisioned(uuid string, meta interface{}, ctx context.Context) error { +func WaitUntilCloudRouterDeprovisioned(uuid string, meta interface{}, ctx context.Context, timeout time.Duration) error { log.Printf("Waiting for Fabric Cloud Router to be deprovisioned, uuid %s", uuid) stateConf := &retry.StateChangeConf{ Pending: []string{ @@ -515,7 +522,7 @@ func WaitUntilCloudRouterDeprovisioned(uuid string, meta interface{}, ctx contex } return dbConn, string(*dbConn.State), nil }, - Timeout: 5 * time.Minute, + Timeout: timeout, Delay: 30 * time.Second, MinTimeout: 30 * time.Second, } diff --git a/equinix/resource_fabric_cloud_router_acc_test.go b/equinix/resource_fabric_cloud_router_acc_test.go index 9aafde673..43060e9e3 100644 --- a/equinix/resource_fabric_cloud_router_acc_test.go +++ b/equinix/resource_fabric_cloud_router_acc_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "testing" + "time" v4 "github.com/equinix-labs/fabric-go/fabric/v4" @@ -41,7 +42,7 @@ func TestAccCloudRouterCreateOnlyRequiredParameters_PFCR(t *testing.T) { resource.TestCheckResourceAttr("equinix_fabric_cloud_router.test", "notifications.0.emails.0", "test@equinix.com"), resource.TestCheckResourceAttr("equinix_fabric_cloud_router.test", "order.0.purchase_order_number", "1-234567"), resource.TestCheckResourceAttr("equinix_fabric_cloud_router.test", "location.0.metro_code", "SV"), - resource.TestCheckResourceAttr("equinix_fabric_cloud_router.test", "package.0.code", "LAB"), + resource.TestCheckResourceAttr("equinix_fabric_cloud_router.test", "package.0.code", "STANDARD"), resource.TestCheckResourceAttrSet("equinix_fabric_cloud_router.test", "project.0.project_id"), resource.TestCheckResourceAttrSet("equinix_fabric_cloud_router.test", "account.0.account_number"), resource.TestCheckResourceAttrSet("equinix_fabric_cloud_router.test", "href"), @@ -79,7 +80,7 @@ func testAccCloudRouterCreateOnlyRequiredParameterConfig_PFCR(name string) strin metro_code = "SV" } package{ - code = "LAB" + code = "STANDARD" } order{ purchase_order_number = "1-234567" @@ -176,7 +177,7 @@ func checkCloudRouterDelete(s *terraform.State) error { if rs.Type != "equinix_fabric_cloud_router" { continue } - err := equinix.WaitUntilCloudRouterDeprovisioned(rs.Primary.ID, acceptance.TestAccProvider.Meta(), ctx) + err := equinix.WaitUntilCloudRouterDeprovisioned(rs.Primary.ID, acceptance.TestAccProvider.Meta(), ctx, 10*time.Minute) if err != nil { return fmt.Errorf("API call failed while waiting for resource deletion") } diff --git a/equinix/resource_fabric_connection.go b/equinix/resource_fabric_connection.go index cee020a77..31540abcc 100644 --- a/equinix/resource_fabric_connection.go +++ b/equinix/resource_fabric_connection.go @@ -592,10 +592,10 @@ func connectionRedundancySch() map[string]*schema.Schema { func resourceFabricConnection() *schema.Resource { return &schema.Resource{ Timeouts: &schema.ResourceTimeout{ - Create: schema.DefaultTimeout(6 * time.Minute), - Update: schema.DefaultTimeout(10 * time.Minute), - Delete: schema.DefaultTimeout(6 * time.Minute), - Read: schema.DefaultTimeout(6 * time.Minute), + Create: schema.DefaultTimeout(15 * time.Minute), + Update: schema.DefaultTimeout(15 * time.Minute), + Delete: schema.DefaultTimeout(15 * time.Minute), + Read: schema.DefaultTimeout(10 * time.Minute), }, ReadContext: resourceFabricConnectionRead, CreateContext: resourceFabricConnectionCreate, @@ -686,13 +686,15 @@ func resourceFabricConnectionCreate(ctx context.Context, d *schema.ResourceData, Project: project, } + start := time.Now() conn, _, err := client.ConnectionsApi.CreateConnection(ctx, createRequest) if err != nil { return diag.FromErr(equinix_errors.FormatFabricError(err)) } d.SetId(conn.Uuid) - if err = waitUntilConnectionIsCreated(d.Id(), meta, ctx); err != nil { + createTimeout := d.Timeout(schema.TimeoutCreate) - 30*time.Second - time.Since(start) + if err = waitUntilConnectionIsCreated(d.Id(), meta, ctx, createTimeout); err != nil { return diag.Errorf("error waiting for connection (%s) to be created: %s", d.Id(), err) } @@ -711,7 +713,8 @@ func resourceFabricConnectionCreate(ctx context.Context, d *schema.ResourceData, return diag.FromErr(equinix_errors.FormatFabricError(patchErr)) } - if _, statusChangeErr := waitForConnectionProviderStatusChange(d.Id(), meta, ctx); statusChangeErr != nil { + createTimeout := d.Timeout(schema.TimeoutCreate) - 30*time.Second - time.Since(start) + if _, statusChangeErr := waitForConnectionProviderStatusChange(d.Id(), meta, ctx, createTimeout); statusChangeErr != nil { return diag.Errorf("error waiting for AWS Approval for connection %s: %v", d.Id(), statusChangeErr) } } @@ -800,7 +803,9 @@ func setFabricMap(d *schema.ResourceData, conn v4.Connection) diag.Diagnostics { func resourceFabricConnectionUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*config.Config).FabricClient ctx = context.WithValue(ctx, v4.ContextAccessToken, meta.(*config.Config).FabricAuthToken) - dbConn, err := verifyConnectionCreated(d.Id(), meta, ctx) + start := time.Now() + updateTimeout := d.Timeout(schema.TimeoutUpdate) - 30*time.Second - time.Since(start) + dbConn, err := verifyConnectionCreated(d.Id(), meta, ctx, updateTimeout) if err != nil { if !strings.Contains(err.Error(), "500") { d.SetId("") @@ -823,7 +828,7 @@ func resourceFabricConnectionUpdate(ctx context.Context, d *schema.ResourceData, continue } - var waitFunction func(uuid string, meta interface{}, ctx context.Context) (v4.Connection, error) + var waitFunction func(uuid string, meta interface{}, ctx context.Context, timeout time.Duration) (v4.Connection, error) if update[0].Op == "replace" { // Update type is either name or bandwidth waitFunction = waitForConnectionUpdateCompletion @@ -832,7 +837,8 @@ func resourceFabricConnectionUpdate(ctx context.Context, d *schema.ResourceData, waitFunction = waitForConnectionProviderStatusChange } - conn, err := waitFunction(d.Id(), meta, ctx) + updateTimeout := d.Timeout(schema.TimeoutUpdate) - 30*time.Second - time.Since(start) + conn, err := waitFunction(d.Id(), meta, ctx, updateTimeout) if err != nil { diags = append(diags, diag.Diagnostic{Severity: 0, Summary: fmt.Sprintf("connection property update completion timeout error: %v [update payload: %v] (other updates will be successful if the payload is not shown)", err, update)}) @@ -845,7 +851,7 @@ func resourceFabricConnectionUpdate(ctx context.Context, d *schema.ResourceData, return append(diags, setFabricMap(d, updatedConn)...) } -func waitForConnectionUpdateCompletion(uuid string, meta interface{}, ctx context.Context) (v4.Connection, error) { +func waitForConnectionUpdateCompletion(uuid string, meta interface{}, ctx context.Context, timeout time.Duration) (v4.Connection, error) { log.Printf("[DEBUG] Waiting for connection update to complete, uuid %s", uuid) stateConf := &retry.StateChangeConf{ Target: []string{"COMPLETED"}, @@ -861,7 +867,7 @@ func waitForConnectionUpdateCompletion(uuid string, meta interface{}, ctx contex } return dbConn, updatableState, nil }, - Timeout: 3 * time.Minute, + Timeout: timeout, Delay: 30 * time.Second, MinTimeout: 30 * time.Second, } @@ -875,7 +881,7 @@ func waitForConnectionUpdateCompletion(uuid string, meta interface{}, ctx contex return dbConn, err } -func waitUntilConnectionIsCreated(uuid string, meta interface{}, ctx context.Context) error { +func waitUntilConnectionIsCreated(uuid string, meta interface{}, ctx context.Context, timeout time.Duration) error { log.Printf("Waiting for connection to be created, uuid %s", uuid) stateConf := &retry.StateChangeConf{ Pending: []string{ @@ -894,7 +900,7 @@ func waitUntilConnectionIsCreated(uuid string, meta interface{}, ctx context.Con } return dbConn, string(*dbConn.State), nil }, - Timeout: 5 * time.Minute, + Timeout: timeout, Delay: 30 * time.Second, MinTimeout: 30 * time.Second, } @@ -904,7 +910,7 @@ func waitUntilConnectionIsCreated(uuid string, meta interface{}, ctx context.Con return err } -func waitForConnectionProviderStatusChange(uuid string, meta interface{}, ctx context.Context) (v4.Connection, error) { +func waitForConnectionProviderStatusChange(uuid string, meta interface{}, ctx context.Context, timeout time.Duration) (v4.Connection, error) { log.Printf("DEBUG: wating for provider status to update. Connection uuid: %s", uuid) stateConf := &retry.StateChangeConf{ Pending: []string{ @@ -922,7 +928,7 @@ func waitForConnectionProviderStatusChange(uuid string, meta interface{}, ctx co } return dbConn, string(*dbConn.Operation.ProviderStatus), nil }, - Timeout: 5 * time.Minute, + Timeout: timeout, Delay: 30 * time.Second, MinTimeout: 30 * time.Second, } @@ -936,7 +942,7 @@ func waitForConnectionProviderStatusChange(uuid string, meta interface{}, ctx co return dbConn, err } -func verifyConnectionCreated(uuid string, meta interface{}, ctx context.Context) (v4.Connection, error) { +func verifyConnectionCreated(uuid string, meta interface{}, ctx context.Context, timeout time.Duration) (v4.Connection, error) { log.Printf("Waiting for connection to be in created state, uuid %s", uuid) stateConf := &retry.StateChangeConf{ Target: []string{ @@ -952,7 +958,7 @@ func verifyConnectionCreated(uuid string, meta interface{}, ctx context.Context) } return dbConn, string(*dbConn.State), nil }, - Timeout: 5 * time.Minute, + Timeout: timeout, Delay: 30 * time.Second, MinTimeout: 30 * time.Second, } @@ -970,6 +976,7 @@ func resourceFabricConnectionDelete(ctx context.Context, d *schema.ResourceData, diags := diag.Diagnostics{} client := meta.(*config.Config).FabricClient ctx = context.WithValue(ctx, v4.ContextAccessToken, meta.(*config.Config).FabricAuthToken) + start := time.Now() _, _, err := client.ConnectionsApi.DeleteConnectionByUuid(ctx, d.Id()) if err != nil { errors, ok := err.(v4.GenericSwaggerError).Model().([]v4.ModelError) @@ -982,14 +989,15 @@ func resourceFabricConnectionDelete(ctx context.Context, d *schema.ResourceData, return diag.FromErr(equinix_errors.FormatFabricError(err)) } - err = WaitUntilConnectionDeprovisioned(d.Id(), meta, ctx) + deleteTimeout := d.Timeout(schema.TimeoutDelete) - 30*time.Second - time.Since(start) + err = WaitUntilConnectionDeprovisioned(d.Id(), meta, ctx, deleteTimeout) if err != nil { return diag.FromErr(fmt.Errorf("API call failed while waiting for resource deletion. Error %v", err)) } return diags } -func WaitUntilConnectionDeprovisioned(uuid string, meta interface{}, ctx context.Context) error { +func WaitUntilConnectionDeprovisioned(uuid string, meta interface{}, ctx context.Context, timeout time.Duration) error { log.Printf("Waiting for connection to be deprovisioned, uuid %s", uuid) stateConf := &retry.StateChangeConf{ Pending: []string{ @@ -1006,7 +1014,7 @@ func WaitUntilConnectionDeprovisioned(uuid string, meta interface{}, ctx context } return dbConn, string(*dbConn.State), nil }, - Timeout: 6 * time.Minute, + Timeout: timeout, Delay: 30 * time.Second, MinTimeout: 30 * time.Second, } diff --git a/equinix/resource_fabric_connection_acc_test.go b/equinix/resource_fabric_connection_acc_test.go index 23739f134..fd052fb08 100644 --- a/equinix/resource_fabric_connection_acc_test.go +++ b/equinix/resource_fabric_connection_acc_test.go @@ -8,6 +8,7 @@ import ( "github.com/equinix/terraform-provider-equinix/internal/acceptance" "os" "testing" + "time" "github.com/equinix/terraform-provider-equinix/internal/config" @@ -82,7 +83,7 @@ func TestAccFabricCreatePort2SPConnection_PFCR(t *testing.T) { resource.TestCheckResourceAttr( "equinix_fabric_connection.test", "z_side.0.access_point.0.location.0.metro_code", "SV"), ), - ExpectNonEmptyPlan: true, + ExpectNonEmptyPlan: false, }, }, }) @@ -372,10 +373,9 @@ func testAccFabricCreateCloudRouter2PortConnectionConfig(name, portUuid string) func TestAccFabricCreateVirtualDevice2NetworkConnection_PNFV(t *testing.T) { connectionTestData := GetFabricEnvConnectionTestData(t) - var virtualDevice, network string + var virtualDevice string if len(connectionTestData) > 0 { virtualDevice = connectionTestData["pnfv"]["virtualDevice"] - network = connectionTestData["pnfv"]["network"] } resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acceptance.TestAccPreCheck(t) }, @@ -383,7 +383,7 @@ func TestAccFabricCreateVirtualDevice2NetworkConnection_PNFV(t *testing.T) { CheckDestroy: CheckConnectionDelete, Steps: []resource.TestStep{ { - Config: testAccFabricCreateVirtualDevice2NetworkConnectionConfig("vd2network_PNFV", virtualDevice, network), + Config: testAccFabricCreateVirtualDevice2NetworkConnectionConfig("vd2network_PNFV", virtualDevice), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr( "equinix_fabric_connection.test", "name", "vd2network_PNFV"), @@ -404,11 +404,13 @@ func TestAccFabricCreateVirtualDevice2NetworkConnection_PNFV(t *testing.T) { resource.TestCheckResourceAttr( "equinix_fabric_connection.test", "a_side.0.access_point.0.interface.0.type", "CLOUD"), resource.TestCheckResourceAttr( - "equinix_fabric_connection.test", "a_side.0.access_point.0.interface.0.id", "7"), + "equinix_fabric_connection.test", "a_side.0.access_point.0.interface.0.id", "6"), + resource.TestCheckResourceAttrSet( + "equinix_fabric_connection.test", "a_side.0.access_point.0.link_protocol.0.vlan_tag"), resource.TestCheckResourceAttr( "equinix_fabric_connection.test", "z_side.0.access_point.0.type", "NETWORK"), - //resource.TestCheckResourceAttr( - // "equinix_fabric_connection.test", "z_side.0.access_point.0.network.0.uuid", connectionTestData["pnfv"]["network"]), + resource.TestCheckResourceAttrSet( + "equinix_fabric_connection.test", "z_side.0.access_point.0.network.0.uuid"), ), ExpectNonEmptyPlan: true, }, @@ -417,9 +419,25 @@ func TestAccFabricCreateVirtualDevice2NetworkConnection_PNFV(t *testing.T) { } -func testAccFabricCreateVirtualDevice2NetworkConnectionConfig(name, virtualDeviceUuid, networkUuid string) string { +func testAccFabricCreateVirtualDevice2NetworkConnectionConfig(name, virtualDeviceUuid string) string { return fmt.Sprintf(` + resource "equinix_fabric_network" "this" { + type = "EVPLAN" + name = "Tf_Network_PNFV" + scope = "REGIONAL" + notifications { + type = "ALL" + emails = ["test@equinix.com","test1@equinix.com"] + } + location { + region = "AMER" + } + project{ + project_id = "4f855852-eb47-4721-8e40-b386a3676abf" + } + } + resource "equinix_fabric_connection" "test" { type = "EVPLAN_VC" name = "%s" @@ -443,7 +461,7 @@ func testAccFabricCreateVirtualDevice2NetworkConnectionConfig(name, virtualDevic } interface { type = "CLOUD" - id = 7 + id = 6 } } } @@ -451,11 +469,11 @@ func testAccFabricCreateVirtualDevice2NetworkConnectionConfig(name, virtualDevic access_point { type = "NETWORK" network { - uuid = "%s" + uuid = equinix_fabric_network.this.id } } } - }`, name, virtualDeviceUuid, networkUuid) + }`, name, virtualDeviceUuid) } func CheckConnectionDelete(s *terraform.State) error { @@ -465,7 +483,7 @@ func CheckConnectionDelete(s *terraform.State) error { if rs.Type != "equinix_fabric_connection" { continue } - err := equinix.WaitUntilConnectionDeprovisioned(rs.Primary.ID, acceptance.TestAccProvider.Meta(), ctx) + err := equinix.WaitUntilConnectionDeprovisioned(rs.Primary.ID, acceptance.TestAccProvider.Meta(), ctx, 10*time.Minute) if err != nil { return fmt.Errorf("API call failed while waiting for resource deletion") } diff --git a/equinix/resource_fabric_network.go b/equinix/resource_fabric_network.go index 0346d08fd..bd46ab48e 100755 --- a/equinix/resource_fabric_network.go +++ b/equinix/resource_fabric_network.go @@ -148,10 +148,10 @@ func fabricNetworkResourceSchema() map[string]*schema.Schema { func resourceFabricNetwork() *schema.Resource { return &schema.Resource{ Timeouts: &schema.ResourceTimeout{ - Create: schema.DefaultTimeout(6 * time.Minute), + Create: schema.DefaultTimeout(10 * time.Minute), Update: schema.DefaultTimeout(10 * time.Minute), - Delete: schema.DefaultTimeout(6 * time.Minute), - Read: schema.DefaultTimeout(6 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + Read: schema.DefaultTimeout(10 * time.Minute), }, ReadContext: resourceFabricNetworkRead, CreateContext: resourceFabricNetworkCreate, @@ -187,13 +187,15 @@ func resourceFabricNetworkCreate(ctx context.Context, d *schema.ResourceData, me Project: project, } + start := time.Now() fabricNetwork, _, err := client.NetworksApi.CreateNetwork(ctx, createRequest) if err != nil { return diag.FromErr(equinix_errors.FormatFabricError(err)) } d.SetId(fabricNetwork.Uuid) - if _, err = waitUntilFabricNetworkIsProvisioned(d.Id(), meta, ctx); err != nil { + createTimeout := d.Timeout(schema.TimeoutCreate) - 30*time.Second - time.Since(start) + if _, err = waitUntilFabricNetworkIsProvisioned(d.Id(), meta, ctx, createTimeout); err != nil { return diag.Errorf("error waiting for Network (%s) to be created: %s", d.Id(), err) } @@ -285,7 +287,9 @@ func getFabricNetworkUpdateRequest(network v4.Network, d *schema.ResourceData) ( func resourceFabricNetworkUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*config.Config).FabricClient ctx = context.WithValue(ctx, v4.ContextAccessToken, meta.(*config.Config).FabricAuthToken) - dbConn, err := waitUntilFabricNetworkIsProvisioned(d.Id(), meta, ctx) + start := time.Now() + updateTimeout := d.Timeout(schema.TimeoutUpdate) - 30*time.Second - time.Since(start) + dbConn, err := waitUntilFabricNetworkIsProvisioned(d.Id(), meta, ctx, updateTimeout) if err != nil { return diag.Errorf("either timed out or errored out while fetching Fabric Network for uuid %s and error %v", d.Id(), err) } @@ -298,8 +302,10 @@ func resourceFabricNetworkUpdate(ctx context.Context, d *schema.ResourceData, me if err != nil { return diag.FromErr(equinix_errors.FormatFabricError(err)) } + updateFg := v4.Network{} - updateFg, err = waitForFabricNetworkUpdateCompletion(d.Id(), meta, ctx) + updateTimeout = d.Timeout(schema.TimeoutUpdate) - 30*time.Second - time.Since(start) + updateFg, err = waitForFabricNetworkUpdateCompletion(d.Id(), meta, ctx, updateTimeout) if err != nil { return diag.Errorf("errored while waiting for successful Fabric Network update, response %v, error %v", res, err) @@ -309,7 +315,7 @@ func resourceFabricNetworkUpdate(ctx context.Context, d *schema.ResourceData, me return setFabricNetworkMap(d, updateFg) } -func waitForFabricNetworkUpdateCompletion(uuid string, meta interface{}, ctx context.Context) (v4.Network, error) { +func waitForFabricNetworkUpdateCompletion(uuid string, meta interface{}, ctx context.Context, timeout time.Duration) (v4.Network, error) { log.Printf("Waiting for Network update to complete, uuid %s", uuid) stateConf := &resource.StateChangeConf{ Target: []string{string(v4.PROVISIONED_NetworkEquinixStatus)}, @@ -321,7 +327,7 @@ func waitForFabricNetworkUpdateCompletion(uuid string, meta interface{}, ctx con } return dbConn, string(*dbConn.Operation.EquinixStatus), nil }, - Timeout: 2 * time.Minute, + Timeout: timeout, Delay: 30 * time.Second, MinTimeout: 30 * time.Second, } @@ -335,7 +341,7 @@ func waitForFabricNetworkUpdateCompletion(uuid string, meta interface{}, ctx con return dbConn, err } -func waitUntilFabricNetworkIsProvisioned(uuid string, meta interface{}, ctx context.Context) (v4.Network, error) { +func waitUntilFabricNetworkIsProvisioned(uuid string, meta interface{}, ctx context.Context, timeout time.Duration) (v4.Network, error) { log.Printf("Waiting for Fabric Network to be provisioned, uuid %s", uuid) stateConf := &resource.StateChangeConf{ Pending: []string{ @@ -352,7 +358,7 @@ func waitUntilFabricNetworkIsProvisioned(uuid string, meta interface{}, ctx cont } return dbConn, string(*dbConn.Operation.EquinixStatus), nil }, - Timeout: 5 * time.Minute, + Timeout: timeout, Delay: 30 * time.Second, MinTimeout: 30 * time.Second, } @@ -370,6 +376,7 @@ func resourceFabricNetworkDelete(ctx context.Context, d *schema.ResourceData, me diags := diag.Diagnostics{} client := meta.(*config.Config).FabricClient ctx = context.WithValue(ctx, v4.ContextAccessToken, meta.(*config.Config).FabricAuthToken) + start := time.Now() _, _, err := client.NetworksApi.DeleteNetworkByUuid(ctx, d.Id()) if err != nil { errors, ok := err.(v4.GenericSwaggerError).Model().([]v4.ModelError) @@ -382,14 +389,15 @@ func resourceFabricNetworkDelete(ctx context.Context, d *schema.ResourceData, me return diag.FromErr(equinix_errors.FormatFabricError(err)) } - err = WaitUntilFabricNetworkDeprovisioned(d.Id(), meta, ctx) + deleteTimeout := d.Timeout(schema.TimeoutDelete) - 30*time.Second - time.Since(start) + err = WaitUntilFabricNetworkDeprovisioned(d.Id(), meta, ctx, deleteTimeout) if err != nil { return diag.Errorf("API call failed while waiting for resource deletion. Error %v", err) } return diags } -func WaitUntilFabricNetworkDeprovisioned(uuid string, meta interface{}, ctx context.Context) error { +func WaitUntilFabricNetworkDeprovisioned(uuid string, meta interface{}, ctx context.Context, timeout time.Duration) error { log.Printf("Waiting for Fabric Network to be deprovisioned, uuid %s", uuid) stateConf := &resource.StateChangeConf{ Pending: []string{ @@ -406,7 +414,7 @@ func WaitUntilFabricNetworkDeprovisioned(uuid string, meta interface{}, ctx cont } return dbConn, string(*dbConn.Operation.EquinixStatus), nil }, - Timeout: 7 * time.Minute, + Timeout: timeout, Delay: 30 * time.Second, MinTimeout: 30 * time.Second, } diff --git a/equinix/resource_fabric_network_acc_test.go b/equinix/resource_fabric_network_acc_test.go index 54466a303..dc444e0c5 100644 --- a/equinix/resource_fabric_network_acc_test.go +++ b/equinix/resource_fabric_network_acc_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "testing" + "time" "github.com/equinix/terraform-provider-equinix/equinix" "github.com/equinix/terraform-provider-equinix/internal/acceptance" @@ -87,7 +88,7 @@ func checkNetworkDelete(s *terraform.State) error { if rs.Type != "equinix_fabric_network" { continue } - err := equinix.WaitUntilFabricNetworkDeprovisioned(rs.Primary.ID, acceptance.TestAccProvider.Meta(), ctx) + err := equinix.WaitUntilFabricNetworkDeprovisioned(rs.Primary.ID, acceptance.TestAccProvider.Meta(), ctx, 10*time.Minute) if err != nil { return fmt.Errorf("API call failed while waiting for resource deletion") } diff --git a/equinix/resource_fabric_routing_protocol.go b/equinix/resource_fabric_routing_protocol.go index 2d7552342..aa5af4bf2 100644 --- a/equinix/resource_fabric_routing_protocol.go +++ b/equinix/resource_fabric_routing_protocol.go @@ -256,10 +256,10 @@ func createFabricRoutingProtocolResourceSchema() map[string]*schema.Schema { func resourceFabricRoutingProtocol() *schema.Resource { return &schema.Resource{ Timeouts: &schema.ResourceTimeout{ - Create: schema.DefaultTimeout(6 * time.Minute), + Create: schema.DefaultTimeout(10 * time.Minute), Update: schema.DefaultTimeout(10 * time.Minute), - Delete: schema.DefaultTimeout(6 * time.Minute), - Read: schema.DefaultTimeout(6 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + Read: schema.DefaultTimeout(10 * time.Minute), }, ReadContext: resourceFabricRoutingProtocolRead, CreateContext: resourceFabricRoutingProtocolCreate, @@ -371,6 +371,8 @@ func resourceFabricRoutingProtocolCreate(ctx context.Context, d *schema.Resource createRequest.DirectIpv6 = nil } } + + start := time.Now() fabricRoutingProtocol, _, err := client.RoutingProtocolsApi.CreateConnectionRoutingProtocol(ctx, createRequest, d.Get("connection_uuid").(string)) if err != nil { return diag.FromErr(equinix_errors.FormatFabricError(err)) @@ -383,7 +385,8 @@ func resourceFabricRoutingProtocolCreate(ctx context.Context, d *schema.Resource d.SetId(fabricRoutingProtocol.RoutingProtocolDirectData.Uuid) } - if _, err = waitUntilRoutingProtocolIsProvisioned(d.Id(), d.Get("connection_uuid").(string), meta, ctx); err != nil { + createTimeout := d.Timeout(schema.TimeoutCreate) - 30*time.Second - time.Since(start) + if _, err = waitUntilRoutingProtocolIsProvisioned(d.Id(), d.Get("connection_uuid").(string), meta, ctx, createTimeout); err != nil { return diag.Errorf("error waiting for RP (%s) to be created: %s", d.Id(), err) } @@ -453,6 +456,7 @@ func resourceFabricRoutingProtocolUpdate(ctx context.Context, d *schema.Resource } } + start := time.Now() updatedRpResp, _, err := client.RoutingProtocolsApi.ReplaceConnectionRoutingProtocolByUuid(ctx, updateRequest, d.Id(), d.Get("connection_uuid").(string)) if err != nil { return diag.FromErr(equinix_errors.FormatFabricError(err)) @@ -467,14 +471,16 @@ func resourceFabricRoutingProtocolUpdate(ctx context.Context, d *schema.Resource changeUuid = updatedRpResp.RoutingProtocolDirectData.Change.Uuid d.SetId(updatedRpResp.RoutingProtocolDirectData.Uuid) } - _, err = waitForRoutingProtocolUpdateCompletion(changeUuid, d.Id(), d.Get("connection_uuid").(string), meta, ctx) + updateTimeout := d.Timeout(schema.TimeoutUpdate) - 30*time.Second - time.Since(start) + _, err = waitForRoutingProtocolUpdateCompletion(changeUuid, d.Id(), d.Get("connection_uuid").(string), meta, ctx, updateTimeout) if err != nil { if !strings.Contains(err.Error(), "500") { d.SetId("") } return diag.FromErr(fmt.Errorf("timeout updating routing protocol: %v", err)) } - updatedProvisionedRpResp, err := waitUntilRoutingProtocolIsProvisioned(d.Id(), d.Get("connection_uuid").(string), meta, ctx) + updateTimeout = d.Timeout(schema.TimeoutUpdate) - 30*time.Second - time.Since(start) + updatedProvisionedRpResp, err := waitUntilRoutingProtocolIsProvisioned(d.Id(), d.Get("connection_uuid").(string), meta, ctx, updateTimeout) if err != nil { return diag.Errorf("error waiting for RP (%s) to be replace updated: %s", d.Id(), err) } @@ -486,6 +492,7 @@ func resourceFabricRoutingProtocolDelete(ctx context.Context, d *schema.Resource diags := diag.Diagnostics{} client := meta.(*config.Config).FabricClient ctx = context.WithValue(ctx, v4.ContextAccessToken, meta.(*config.Config).FabricAuthToken) + start := time.Now() _, _, err := client.RoutingProtocolsApi.DeleteConnectionRoutingProtocolByUuid(ctx, d.Id(), d.Get("connection_uuid").(string)) if err != nil { errors, ok := err.(v4.GenericSwaggerError).Model().([]v4.ModelError) @@ -498,7 +505,8 @@ func resourceFabricRoutingProtocolDelete(ctx context.Context, d *schema.Resource return diag.FromErr(equinix_errors.FormatFabricError(err)) } - err = WaitUntilRoutingProtocolIsDeprovisioned(d.Id(), d.Get("connection_uuid").(string), meta, ctx) + deleteTimeout := d.Timeout(schema.TimeoutDelete) - 30*time.Second - time.Since(start) + err = WaitUntilRoutingProtocolIsDeprovisioned(d.Id(), d.Get("connection_uuid").(string), meta, ctx, deleteTimeout) if err != nil { return diag.FromErr(fmt.Errorf("API call failed while waiting for resource deletion. Error %v", err)) } @@ -546,7 +554,7 @@ func setFabricRoutingProtocolMap(d *schema.ResourceData, rp v4.RoutingProtocolDa return diags } -func waitUntilRoutingProtocolIsProvisioned(uuid string, connUuid string, meta interface{}, ctx context.Context) (v4.RoutingProtocolData, error) { +func waitUntilRoutingProtocolIsProvisioned(uuid string, connUuid string, meta interface{}, ctx context.Context, timeout time.Duration) (v4.RoutingProtocolData, error) { log.Printf("Waiting for routing protocol to be provisioned, uuid %s", uuid) stateConf := &retry.StateChangeConf{ Pending: []string{ @@ -570,7 +578,7 @@ func waitUntilRoutingProtocolIsProvisioned(uuid string, connUuid string, meta in } return dbConn, state, nil }, - Timeout: 5 * time.Minute, + Timeout: timeout, Delay: 30 * time.Second, MinTimeout: 30 * time.Second, } @@ -585,7 +593,7 @@ func waitUntilRoutingProtocolIsProvisioned(uuid string, connUuid string, meta in return dbConn, err } -func WaitUntilRoutingProtocolIsDeprovisioned(uuid string, connUuid string, meta interface{}, ctx context.Context) error { +func WaitUntilRoutingProtocolIsDeprovisioned(uuid string, connUuid string, meta interface{}, ctx context.Context, timeout time.Duration) error { log.Printf("Waiting for routing protocol to be deprovisioned, uuid %s", uuid) /* check if resource is not found */ @@ -601,7 +609,7 @@ func WaitUntilRoutingProtocolIsDeprovisioned(uuid string, connUuid string, meta return dbConn, strconv.Itoa(resp.StatusCode), nil }, - Timeout: 5 * time.Minute, + Timeout: timeout, Delay: 30 * time.Second, MinTimeout: 30 * time.Second, } @@ -610,7 +618,7 @@ func WaitUntilRoutingProtocolIsDeprovisioned(uuid string, connUuid string, meta return err } -func waitForRoutingProtocolUpdateCompletion(rpChangeUuid string, uuid string, connUuid string, meta interface{}, ctx context.Context) (v4.RoutingProtocolChangeData, error) { +func waitForRoutingProtocolUpdateCompletion(rpChangeUuid string, uuid string, connUuid string, meta interface{}, ctx context.Context, timeout time.Duration) (v4.RoutingProtocolChangeData, error) { log.Printf("Waiting for routing protocol update to complete, uuid %s", uuid) stateConf := &retry.StateChangeConf{ Target: []string{"COMPLETED"}, @@ -626,7 +634,7 @@ func waitForRoutingProtocolUpdateCompletion(rpChangeUuid string, uuid string, co } return dbConn, updatableState, nil }, - Timeout: 2 * time.Minute, + Timeout: timeout, Delay: 30 * time.Second, MinTimeout: 30 * time.Second, } diff --git a/equinix/resource_fabric_routing_protocol_acc_test.go b/equinix/resource_fabric_routing_protocol_acc_test.go index cb790aec0..94dcab672 100644 --- a/equinix/resource_fabric_routing_protocol_acc_test.go +++ b/equinix/resource_fabric_routing_protocol_acc_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "testing" + "time" "github.com/equinix/terraform-provider-equinix/equinix" "github.com/equinix/terraform-provider-equinix/internal/acceptance" @@ -20,7 +21,7 @@ import ( // The FCR, Connection and RPs will already be created in the resource test, so the // data_source tests will just leverage the RPs there to retrieve the data and check results -func TestAccFabricCreateDirectRoutingProtocol_PFCR_A(t *testing.T) { +func TestAccFabricCreateRoutingProtocols_PFCR(t *testing.T) { ports := GetFabricEnvPorts(t) var portUuid string @@ -202,7 +203,7 @@ func checkRoutingProtocolDelete(s *terraform.State) error { if rs.Type != "equinix_fabric_routing_protocol" { continue } - err := equinix.WaitUntilRoutingProtocolIsDeprovisioned(rs.Primary.ID, rs.Primary.Attributes["connection_uuid"], acceptance.TestAccProvider.Meta(), ctx) + err := equinix.WaitUntilRoutingProtocolIsDeprovisioned(rs.Primary.ID, rs.Primary.Attributes["connection_uuid"], acceptance.TestAccProvider.Meta(), ctx, 10*time.Minute) if err != nil { return fmt.Errorf("API call failed while waiting for resource deletion") } diff --git a/equinix/resource_fabric_service_profile.go b/equinix/resource_fabric_service_profile.go index 171392859..ed5791283 100644 --- a/equinix/resource_fabric_service_profile.go +++ b/equinix/resource_fabric_service_profile.go @@ -525,10 +525,10 @@ func createLinkProtocolConfigSch() map[string]*schema.Schema { func resourceFabricServiceProfile() *schema.Resource { return &schema.Resource{ Timeouts: &schema.ResourceTimeout{ - Create: schema.DefaultTimeout(6 * time.Minute), + Create: schema.DefaultTimeout(10 * time.Minute), Update: schema.DefaultTimeout(10 * time.Minute), - Delete: schema.DefaultTimeout(6 * time.Minute), - Read: schema.DefaultTimeout(6 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + Read: schema.DefaultTimeout(10 * time.Minute), }, ReadContext: resourceFabricServiceProfileRead, CreateContext: resourceFabricServiceProfileCreate, @@ -632,9 +632,11 @@ func resourceFabricServiceProfileUpdate(ctx context.Context, d *schema.ResourceD uuid := d.Id() updateRequest := getServiceProfileRequestPayload(d) + start := time.Now() + updateTimeout := d.Timeout(schema.TimeoutUpdate) - 30*time.Second - time.Since(start) var err error var eTag int64 = 0 - _, err, eTag = waitForActiveServiceProfileAndPopulateETag(uuid, meta, ctx) + _, err, eTag = waitForActiveServiceProfileAndPopulateETag(uuid, meta, ctx, updateTimeout) if err != nil { if !strings.Contains(err.Error(), "500") { d.SetId("") @@ -646,8 +648,10 @@ func resourceFabricServiceProfileUpdate(ctx context.Context, d *schema.ResourceD if err != nil { return diag.FromErr(equinix_errors.FormatFabricError(err)) } + + updateTimeout = d.Timeout(schema.TimeoutUpdate) - 30*time.Second - time.Since(start) updatedServiceProfile := v4.ServiceProfile{} - updatedServiceProfile, err = waitForServiceProfileUpdateCompletion(uuid, meta, ctx) + updatedServiceProfile, err = waitForServiceProfileUpdateCompletion(uuid, meta, ctx, updateTimeout) if err != nil { if !strings.Contains(err.Error(), "500") { d.SetId("") @@ -658,7 +662,7 @@ func resourceFabricServiceProfileUpdate(ctx context.Context, d *schema.ResourceD return setFabricServiceProfileMap(d, updatedServiceProfile) } -func waitForServiceProfileUpdateCompletion(uuid string, meta interface{}, ctx context.Context) (v4.ServiceProfile, error) { +func waitForServiceProfileUpdateCompletion(uuid string, meta interface{}, ctx context.Context, timeout time.Duration) (v4.ServiceProfile, error) { log.Printf("Waiting for service profile update to complete, uuid %s", uuid) stateConf := &retry.StateChangeConf{ Target: []string{"COMPLETED"}, @@ -671,7 +675,7 @@ func waitForServiceProfileUpdateCompletion(uuid string, meta interface{}, ctx co updatableState := "COMPLETED" return dbServiceProfile, updatableState, nil }, - Timeout: 1 * time.Minute, + Timeout: timeout, Delay: 10 * time.Second, MinTimeout: 10 * time.Second, } @@ -685,7 +689,7 @@ func waitForServiceProfileUpdateCompletion(uuid string, meta interface{}, ctx co return dbSp, err } -func waitForActiveServiceProfileAndPopulateETag(uuid string, meta interface{}, ctx context.Context) (v4.ServiceProfile, error, int64) { +func waitForActiveServiceProfileAndPopulateETag(uuid string, meta interface{}, ctx context.Context, timeout time.Duration) (v4.ServiceProfile, error, int64) { log.Printf("Waiting for service profile to be in active state, uuid %s", uuid) var eTag int64 = 0 stateConf := &retry.StateChangeConf{ @@ -709,7 +713,7 @@ func waitForActiveServiceProfileAndPopulateETag(uuid string, meta interface{}, c } return dbServiceProfile, updatableState, nil }, - Timeout: 1 * time.Minute, + Timeout: timeout, Delay: 10 * time.Second, MinTimeout: 10 * time.Second, } @@ -729,12 +733,14 @@ func resourceFabricServiceProfileDelete(ctx context.Context, d *schema.ResourceD if uuid == "" { return diag.Errorf("No uuid found for Service Profile Deletion %v ", uuid) } + start := time.Now() _, _, err := client.ServiceProfilesApi.DeleteServiceProfileByUuid(ctx, uuid) if err != nil { return diag.FromErr(equinix_errors.FormatFabricError(err)) } - waitErr := WaitAndCheckServiceProfileDeleted(uuid, client, ctx) + deleteTimeout := d.Timeout(schema.TimeoutDelete) - 30*time.Second - time.Since(start) + waitErr := WaitAndCheckServiceProfileDeleted(uuid, client, ctx, deleteTimeout) if waitErr != nil { return diag.Errorf("Error while waiting for Service Profile deletion: %v", waitErr) } @@ -742,7 +748,7 @@ func resourceFabricServiceProfileDelete(ctx context.Context, d *schema.ResourceD return diags } -func WaitAndCheckServiceProfileDeleted(uuid string, client *v4.APIClient, ctx context.Context) error { +func WaitAndCheckServiceProfileDeleted(uuid string, client *v4.APIClient, ctx context.Context, timeout time.Duration) error { log.Printf("Waiting for service profile to be in deleted, uuid %s", uuid) stateConf := &retry.StateChangeConf{ Target: []string{string(v4.DELETED_ServiceProfileStateEnum)}, @@ -757,7 +763,7 @@ func WaitAndCheckServiceProfileDeleted(uuid string, client *v4.APIClient, ctx co } return dbConn, updatableState, nil }, - Timeout: 1 * time.Minute, + Timeout: timeout, Delay: 10 * time.Second, MinTimeout: 10 * time.Second, } diff --git a/equinix/resource_fabric_service_profile_acc_test.go b/equinix/resource_fabric_service_profile_acc_test.go index f8774de62..f5def517c 100644 --- a/equinix/resource_fabric_service_profile_acc_test.go +++ b/equinix/resource_fabric_service_profile_acc_test.go @@ -7,6 +7,7 @@ import ( "github.com/equinix/terraform-provider-equinix/internal/acceptance" "github.com/equinix/terraform-provider-equinix/internal/config" "testing" + "time" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" @@ -160,7 +161,7 @@ func checkServiceProfileDelete(s *terraform.State) error { if rs.Type != "equinix_fabric_service_profile" { continue } - err := equinix.WaitAndCheckServiceProfileDeleted(rs.Primary.ID, client, ctx) + err := equinix.WaitAndCheckServiceProfileDeleted(rs.Primary.ID, client, ctx, 10*time.Minute) if err != nil { return fmt.Errorf("API call failed while waiting for resource deletion: %v", err) } From 4d0b305f79b867119c78909bf6ee031568705f6a Mon Sep 17 00:00:00 2001 From: Charles Treatman Date: Fri, 29 Mar 2024 10:16:04 -0500 Subject: [PATCH 2/2] fix: correctly handle null MD5 in project BGP settings (#632) During the framework migration of the project resource, we missed that the framework-based resource doesn't correctly handle null values for the `md5` attribute in BGP config. This updates the basic project BGP test to cover that aspect of the project resource and updates the project model to avoid setting the in-state md5 value to `""` when it should be `nil`. Closes #630 --- internal/resources/metal/project/models.go | 4 ++- .../resources/metal/project/resource_test.go | 25 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/internal/resources/metal/project/models.go b/internal/resources/metal/project/models.go index f38053958..f50de7118 100644 --- a/internal/resources/metal/project/models.go +++ b/internal/resources/metal/project/models.go @@ -105,10 +105,12 @@ func parseBGPConfig(ctx context.Context, bgpConfig *metalv1.BgpConfig) fwtypes.L bgpConfigResourceModel[0] = BGPConfigModel{ DeploymentType: types.StringValue(string(bgpConfig.GetDeploymentType())), ASN: types.Int64Value(int64(bgpConfig.GetAsn())), - MD5: types.StringValue(bgpConfig.GetMd5()), Status: types.StringValue(string(bgpConfig.GetStatus())), MaxPrefix: types.Int64Value(int64(bgpConfig.GetMaxPrefix())), } + if bgpConfig.Md5.Get() != nil { + bgpConfigResourceModel[0].MD5 = types.StringValue(bgpConfig.GetMd5()) + } return fwtypes.NewListNestedObjectValueOfValueSlice[BGPConfigModel](ctx, bgpConfigResourceModel) } return fwtypes.NewListNestedObjectValueOfNull[BGPConfigModel](ctx) diff --git a/internal/resources/metal/project/resource_test.go b/internal/resources/metal/project/resource_test.go index 69e961678..f0a6c26e0 100644 --- a/internal/resources/metal/project/resource_test.go +++ b/internal/resources/metal/project/resource_test.go @@ -132,6 +132,9 @@ func TestAccMetalProject_BGPBasic(t *testing.T) { resource.TestCheckResourceAttr( "equinix_metal_project.foobar", "bgp_config.0.md5", "2SFsdfsg43"), + resource.TestCheckResourceAttr( + "equinix_metal_project.foobar", "bgp_config.0.asn", + "65000"), ), }, { @@ -139,6 +142,17 @@ func TestAccMetalProject_BGPBasic(t *testing.T) { ImportState: true, ImportStateVerify: true, }, + { + Config: testAccMetalProjectConfig_BGPWithoutMD5(rInt), + Check: resource.ComposeTestCheckFunc( + testAccMetalProjectExists("equinix_metal_project.foobar", &project), + resource.TestCheckNoResourceAttr( + "equinix_metal_project.foobar", "bgp_config.0.md5"), + resource.TestCheckResourceAttr( + "equinix_metal_project.foobar", "bgp_config.0.asn", + "65000"), + ), + }, }, }) } @@ -343,6 +357,17 @@ resource "equinix_metal_project" "foobar" { }`, r) } +func testAccMetalProjectConfig_BGPWithoutMD5(r int) string { + return fmt.Sprintf(` +resource "equinix_metal_project" "foobar" { + name = "tfacc-project-%d" + bgp_config { + deployment_type = "local" + asn = 65000 + } +}`, r) +} + func testAccMetalProjectConfig_BGP(r int, pass string) string { return fmt.Sprintf(` resource "equinix_metal_project" "foobar" {