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: Update CRUD for metal port to support timeouts #377

Merged
merged 5 commits into from
Sep 27, 2023
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
11 changes: 11 additions & 0 deletions docs/resources/equinix_metal_port.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ ports.
attached VLANs (from `vlan_ids` parameter).
* `reset_on_delete` - (Optional) Behavioral setting to reset the port to default settings (layer3 bonded mode without any vlan attached) before delete/destroy.

### Timeouts

The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/configuration/resources#operation-timeouts) for certain actions:

These timeout includes the time to disbond, convert to L2/L3, bond and update native vLAN.

* `create` - (Defaults to 30 mins) Used when creating the Port.
* `update` - (Defaults to 30 mins) Used when updating the Port.
* `delete` - (Defaults to 30 mins) Used when deleting the Port.


## Attributes Reference

In addition to all arguments above, the following attributes are exported:
Expand Down
2 changes: 1 addition & 1 deletion equinix/data_source_metal_port.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (

func dataSourceMetalPort() *schema.Resource {
return &schema.Resource{
Read: resourceMetalPortRead,
ReadWithoutTimeout: diagnosticsWrapper(resourceMetalPortRead),

Schema: map[string]*schema.Schema{
"port_id": {
Expand Down
72 changes: 39 additions & 33 deletions equinix/port_helpers.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
package equinix

import (
"context"
"fmt"
"strings"
"time"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/packethost/packngo"
"github.com/pkg/errors"
)

type portVlanAction func(*packngo.PortAssignRequest) (*packngo.Port, *packngo.Response, error)

type ClientPortResource struct {
Client *packngo.Client
Port *packngo.Port
Expand Down Expand Up @@ -126,20 +127,7 @@ func specifiedVlanIds(d *schema.ResourceData) []string {
return []string{}
}

func processVlansOnPort(port *packngo.Port, vlanIds []string, f portVlanAction) (*packngo.Port, error) {
aayushrangwala marked this conversation as resolved.
Show resolved Hide resolved
par := packngo.PortAssignRequest{PortID: port.ID}
for _, vId := range vlanIds {
par.VirtualNetworkID = vId
var err error
port, _, err = f(&par)
if err != nil {
return nil, err
}
}
return port, nil
}

func batchVlans(removeOnly bool) func(*ClientPortResource) error {
func batchVlans(ctx context.Context, start time.Time, removeOnly bool) func(*ClientPortResource) error {
return func(cpr *ClientPortResource) error {
var vlansToAssign []string
var currentNative string
Expand Down Expand Up @@ -171,35 +159,53 @@ func batchVlans(removeOnly bool) func(*ClientPortResource) error {
Native: &native,
})
}
return createAndWaitForBatch(cpr.Client, cpr.Port.ID, vacr)
return createAndWaitForBatch(ctx, start, cpr, vacr)
}
}

func createAndWaitForBatch(c *packngo.Client, portID string, vacr *packngo.VLANAssignmentBatchCreateRequest) error {
func createAndWaitForBatch(ctx context.Context, start time.Time, cpr *ClientPortResource, vacr *packngo.VLANAssignmentBatchCreateRequest) error {
if len(vacr.VLANAssignments) == 0 {
return nil
}

portID := cpr.Port.ID
c := cpr.Client

b, _, err := c.VLANAssignments.CreateBatch(portID, vacr, nil)
if err != nil {
return fmt.Errorf("vlan assignment batch could not be created: %w", err)
}

// 15 minutes = 180 * 5sec-retry
for i := 0; i < 180; i++ {
<-time.After(5 * time.Second)
b, _, err := c.VLANAssignments.GetBatch(portID, b.ID, nil)
if err != nil {
return fmt.Errorf("vlan assignment batch %s could not be polled: %w", b.ID, err)
}
if b.State == packngo.VLANAssignmentBatchCompleted {
return nil
}
if b.State == packngo.VLANAssignmentBatchFailed {
return fmt.Errorf("vlan assignment batch %s provisioning failed: %s", b.ID, strings.Join(b.ErrorMessages, "; "))
}
deadline, _ := ctx.Deadline()
// originally set timeout in ctx by TF
ctxTimeout := deadline.Sub(start)

stateChangeConf := &retry.StateChangeConf{
Delay: 5 * time.Second,
Pending: []string{string(packngo.VLANAssignmentBatchQueued), string(packngo.VLANAssignmentBatchInProgress)},
Target: []string{string(packngo.VLANAssignmentBatchCompleted)},
MinTimeout: 5 * time.Second,
Timeout: ctxTimeout - time.Since(start) - 30*time.Second,
Refresh: func() (result interface{}, state string, err error) {
b, _, err := c.VLANAssignments.GetBatch(portID, b.ID, nil)
switch b.State {
case packngo.VLANAssignmentBatchFailed:
return b, string(packngo.VLANAssignmentBatchFailed),
fmt.Errorf("vlan assignment batch %s provisioning failed: %s", b.ID, strings.Join(b.ErrorMessages, "; "))
case packngo.VLANAssignmentBatchCompleted:
return b, string(packngo.VLANAssignmentBatchCompleted), nil
default:
if err != nil {
return b, "", fmt.Errorf("vlan assignment batch %s could not be polled: %w", b.ID, err)
}
return b, string(b.State), err
}
},
}

return fmt.Errorf("vlan assignment batch %s is not complete after timeout", b.ID)
if _, err = stateChangeConf.WaitForStateContext(ctx); err != nil {
return errors.Wrapf(err, "vlan assignment batch %s is not complete after timeout", b.ID)
}
return nil
}

func updateNativeVlan(cpr *ClientPortResource) error {
Expand Down
33 changes: 18 additions & 15 deletions equinix/resource_metal_port.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package equinix

import (
"context"
"log"
"time"

Expand All @@ -21,17 +22,17 @@ var (
func resourceMetalPort() *schema.Resource {
return &schema.Resource{
Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(20 * time.Minute),
Update: schema.DefaultTimeout(20 * time.Minute),
Delete: schema.DefaultTimeout(20 * time.Minute),
Create: schema.DefaultTimeout(30 * time.Minute),
Update: schema.DefaultTimeout(30 * time.Minute),
Delete: schema.DefaultTimeout(30 * time.Minute),
},
Read: resourceMetalPortRead,
ReadWithoutTimeout: diagnosticsWrapper(resourceMetalPortRead),
// Create and Update are the same func
Create: resourceMetalPortUpdate,
Update: resourceMetalPortUpdate,
Delete: resourceMetalPortDelete,
CreateContext: diagnosticsWrapper(resourceMetalPortUpdate),
UpdateContext: diagnosticsWrapper(resourceMetalPortUpdate),
DeleteContext: diagnosticsWrapper(resourceMetalPortDelete),
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
StateContext: schema.ImportStatePassthroughContext,
},

Schema: map[string]*schema.Schema{
Expand Down Expand Up @@ -116,31 +117,32 @@ func resourceMetalPort() *schema.Resource {
}
}

func resourceMetalPortUpdate(d *schema.ResourceData, meta interface{}) error {
func resourceMetalPortUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) error {
start := time.Now()
cpr, _, err := getClientPortResource(d, meta)
if err != nil {
return friendlyError(err)
}

for _, f := range [](func(*ClientPortResource) error){
portSanityChecks,
batchVlans(true),
batchVlans(ctx, start, true),
makeDisbond,
convertToL2,
makeBond,
convertToL3,
batchVlans(false),
batchVlans(ctx, start, false),
updateNativeVlan,
} {
if err := f(cpr); err != nil {
return friendlyError(err)
}
}

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

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

Expand Down Expand Up @@ -195,9 +197,10 @@ func resourceMetalPortRead(d *schema.ResourceData, meta interface{}) error {
return setMap(d, m)
}

func resourceMetalPortDelete(d *schema.ResourceData, meta interface{}) error {
func resourceMetalPortDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) error {
resetRaw, resetOk := d.GetOk("reset_on_delete")
if resetOk && resetRaw.(bool) {
start := time.Now()
cpr, resp, err := getClientPortResource(d, meta)
if ignoreResponseErrors(httpForbidden, httpNotFound)(resp, err) != nil {
return err
Expand All @@ -219,7 +222,7 @@ func resourceMetalPortDelete(d *schema.ResourceData, meta interface{}) error {
return err
}
for _, f := range [](func(*ClientPortResource) error){
displague marked this conversation as resolved.
Show resolved Hide resolved
batchVlans(true),
batchVlans(ctx, start, true),
makeBond,
convertToL3,
} {
Expand Down
Loading
Loading