Skip to content

Commit

Permalink
refactor: update r/vmc
Browse files Browse the repository at this point in the history
Refactors `r/vmc`.

Signed-off-by: Ryan Johnson <[email protected]>
  • Loading branch information
tenthirtyam committed Feb 2, 2025
1 parent 0cb25bc commit d8bd705
Show file tree
Hide file tree
Showing 3 changed files with 158 additions and 133 deletions.
1 change: 0 additions & 1 deletion hcx/activation.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ func GetActivate(c *Client) (ActivateBody, error) {

// DeleteActivate sends a request to remove the activation configuration using the provided body and returns the
// resulting ActivateBody object. Returns an error if the request fails or the response cannot be parsed.

func DeleteActivate(c *Client, body ActivateBody) (ActivateBody, error) {

resp := ActivateBody{}
Expand Down
1 change: 1 addition & 0 deletions hcx/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const (
// VMC
VmcMaxRetries = 12
VmcRetryInterval = 10 * time.Second
VmcMaxRetryInterval = 5 * time.Minute
VmcActivationActiveStatus = "ACTIVE"
VmcActivationFailedStatus = "ACTIVATION_FAILED"
VmcDeactivationInactiveStatus = "DE-ACTIVATED"
Expand Down
289 changes: 157 additions & 132 deletions hcx/resource_vmc.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ package hcx

import (
"context"
"log"
"math/rand"
"time"

"github.com/vmware/terraform-provider-hcx/hcx/constants"
Expand Down Expand Up @@ -58,203 +58,228 @@ func resourceVmc() *schema.Resource {

// resourceVmcCreate creates the VMware Cloud on AWS configuration.
func resourceVmcCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {

client := m.(*Client)

token := client.Token
sddcName := d.Get("sddc_name").(string)
sddcID := d.Get("sddc_id").(string)

// Authenticate with VMware Cloud Services
accessToken, err := VmcAuthenticate(token)
if err != nil {
return diag.FromErr(err)
}

err = CloudAuthenticate(client, accessToken)
if err != nil {
return diag.FromErr(err)
// Authenticate and fetch the SDDC details.
sddc, diags := authenticateAndFetchSDDC(ctx, d, client)
if diags != nil {
return diags
}

var sddc SDDC
if sddcID != "" {
sddc, err = GetSddcByID(client, sddcID)
} else {
sddc, err = GetSddcByName(client, sddcName)
}

if err != nil {
return diag.FromErr(err)
}

// Check if already activated.
// Validate if the SDDC is already activated.
if sddc.DeploymentStatus == constants.VmcActivationActiveStatus {
return diag.Errorf("Already activated")
return diag.Errorf("sddc is already activated")
}

// Activate HCX.
_, err = ActivateHcxOnSDDC(client, sddc.ID)
// Trigger the activation of HCX on the specified SDDC.
_, err := ActivateHcxOnSDDC(client, sddc.ID)
if err != nil {
return diag.FromErr(err)
return diag.Errorf("failed to activate hcx on the sddc: %s", err.Error())
}

// Wait for task to be completed.
errcount := 0
for {
if sddcID != "" {
sddc, err = GetSddcByID(client, sddcID)
} else {
sddc, err = GetSddcByName(client, sddcName)
}
if err != nil {
// Attempt to bypass recurring situation where the HCX API
// returns status 502 with a proxy server error, and an HTML response
// instead of JSON.
errcount++
hclog.Default().Info("[INFO] - resourceVmcCreate() - Error retrieving SDDC status: ", "error", err.Error(), "Errcount:", errcount)
if errcount > 12 {
// Poll the activation status until the process completes successfully or fails.
for retries := 0; retries < constants.VmcMaxRetries; retries++ {
// Fetch the updated SDDC details.
sddc, diags = authenticateAndFetchSDDC(ctx, d, client)
if diags != nil {
hclog.Default().Info("[INFO] - resourceVmcCreate() - error retrieving SDDC status; retrying...",
"sddc_id", sddc.ID, "sddc_name", sddc.Name, "error", diags[0].Summary, "RetryCount", retries)

// Exit if the retry limit is exceeded.
if retries == constants.VmcMaxRetries-1 {
return diags
}

// Backoff before retrying.
waitTime := calculateBackoff(retries)
if err := waitWithContext(ctx, waitTime); err != nil { // Respect context cancellations.
return diag.FromErr(err)
}
continue
}

if sddc.DeploymentStatus == constants.VmcActivationActiveStatus {
break
}
// Check the current deployment status of the SDDC.
switch sddc.DeploymentStatus {
case constants.VmcActivationActiveStatus:
// Activation successful. Refresh the resource state by calling "read".
return resourceVmcRead(ctx, d, m)

case constants.VmcActivationFailedStatus:
// Explicit activation failure. Return an appropriate error message.
return diag.Errorf("activation failed with status: %s", constants.VmcActivationFailedStatus)

if sddc.DeploymentStatus == constants.VmcActivationFailedStatus {
return diag.Errorf("Activation failed")
default:
hclog.Default().Warn("unknown SDDC status during polling", "status", sddc.DeploymentStatus)
}

time.Sleep(constants.VmcRetryInterval)
// Wait before the next polling attempt.
if err := waitWithContext(ctx, constants.VmcRetryInterval); err != nil {
return diag.FromErr(err)
}
}

return resourceVmcRead(ctx, d, m)
// If retry limit is reached without resolution, return an error.
return diag.Errorf("maximum retries reached while activating the SDDC")
}

// resourceVmcRead retrieves the VMware Cloud on AWS configuration.
func resourceVmcRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
var diags diag.Diagnostics

client := m.(*Client)

token := client.Token
sddcName := d.Get("sddc_name").(string)
sddcID := d.Get("sddc_id").(string)

log.Printf("******************************************************************\n")
log.Printf("token: %s, sddc_name: %s, sddc: %s \n", token, sddcName, sddcID)
log.Printf("******************************************************************\n")

if sddcName == "" && sddcID == "" {
return diag.Errorf("SDDC name or Id must be specified")
// Authenticate and fetch the SDDC details.
sddc, diags := authenticateAndFetchSDDC(ctx, d, client)
if diags != nil {
return diags
}

// Authenticate with VMware Cloud Services
accessToken, err := VmcAuthenticate(token)
if err != nil {
return diag.FromErr(err)
// Set resource data fields with retrieved SDDC details.
if err := d.Set("cloud_url", sddc.CloudURL); err != nil {
return diag.Errorf("failed to set 'cloud_url': %s", err.Error())
}

err = CloudAuthenticate(client, accessToken)
if err != nil {
return diag.FromErr(err)
}

log.Printf("****************")
log.Printf("[Client inside]: %+v ", client)
log.Printf("****************")

var sddc SDDC
if sddcID != "" {
sddc, err = GetSddcByID(client, sddcID)
} else {
sddc, err = GetSddcByName(client, sddcName)
if err := d.Set("cloud_name", sddc.CloudName); err != nil {
return diag.Errorf("failed to set 'cloud_name': %s", err.Error())
}
if err != nil {
return diag.FromErr(err)
if err := d.Set("cloud_type", sddc.CloudType); err != nil {
return diag.Errorf("failed to set 'cloud_type': %s", err.Error())
}

d.SetId(sddc.ID)
d.Set("cloud_url", sddc.CloudURL)
d.Set("cloud_name", sddc.CloudName)
d.Set("cloud_type", sddc.CloudType)

return diags
// Return diagnostics response.
return nil
}

// resourceVmcUpdate updates the VMware Cloud on AWS resource configuration.
func resourceVmcUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
// Validate immutable fields to prevent unsupported updates.
if d.HasChange("sddc_id") || d.HasChange("sddc_name") {
return diag.Errorf("opdating 'sddc_id' or 'sddc_name' is not supported")
}

// Return the current state using Read.
return resourceVmcRead(ctx, d, m)
}

// resourceVmcDelete removes the VMware Cloud on AWS configuration and clears the state of the resource in the schema.
func resourceVmcDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
var diags diag.Diagnostics

client := m.(*Client)

// Authenticate and fetch the SDDC details.
sddc, diags := authenticateAndFetchSDDC(ctx, d, client)
if diags != nil {
return diags
}

// Trigger the deactivation of HCX on the specified SDDC.
_, err := DeactivateHcxOnSDDC(client, sddc.ID)
if err != nil {
return diag.Errorf("failed to deactivate hcx on the sddc: %s", err.Error())
}

// Poll the deactivation status until the process completes successfully or fails.
for retries := 0; retries < constants.VmcMaxRetries; retries++ {
// Fetch the updated status of the SDDC.
sddc, diags = authenticateAndFetchSDDC(ctx, d, client)
if diags != nil {
hclog.Default().Info("[INFO] - resourceVmcDelete() - error retrieving SDDC status; retrying...",
"sddc_id", sddc.ID, "sddc_name", sddc.Name, "error", diags[0].Summary, "RetryCount", retries)

// Exit if the retry limit is exceeded.
if retries == constants.VmcMaxRetries-1 {
return diags
}

// Backoff before retrying.
waitTime := calculateBackoff(retries)
if err := waitWithContext(ctx, waitTime); err != nil {
return diag.FromErr(err)
}
continue
}

// Evaluate the current deployment status of the SDDC.
switch sddc.DeploymentStatus {
case constants.VmcDeactivationInactiveStatus:
// Deactivation successful. Exit the deletion process.
return nil

case constants.VmcDeactivationFailedStatus:
// Explicit deactivation failure. Return an appropriate error message.
return diag.Errorf("deactivation failed with status: %s", constants.VmcDeactivationFailedStatus)

case "":
// SDDC resource already missing or deleted.
hclog.Default().Info("[INFO] - resourceVmcDelete() - SDDC already missing or deleted.")
return nil

default:
hclog.Default().Warn("unknown SDDC status during polling", "status", sddc.DeploymentStatus)
}

// Wait before polling again.
if err := waitWithContext(ctx, constants.VmcRetryInterval); err != nil {
return diag.FromErr(err)
}
}

// If retry limit is reached without resolution, return an error.
return diag.Errorf("maximum retries reached while deleting the SDDC")
}

// authenticateAndFetchSDDC authenticates with VMware Cloud and HCX APIs, and retrieves SDDC details by name or ID.
func authenticateAndFetchSDDC(ctx context.Context, d *schema.ResourceData, client *Client) (SDDC, diag.Diagnostics) {
// Extract input parameters.
token := client.Token
sddcName := d.Get("sddc_name").(string)
sddcID := d.Get("sddc_id").(string)

// Authenticate with VMware Cloud Services
// Validate inputs.
if sddcName == "" && sddcID == "" {
return SDDC{}, diag.Errorf("neither 'sddc_name' nor 'sddc_id' was specified")
}

// Authenticate with VMware Cloud Services.
accessToken, err := VmcAuthenticate(token)
if err != nil {
return diag.FromErr(err)
return SDDC{}, diag.Errorf("failed to authenticate with VMware Cloud Services: %s", err.Error())
}

// Authenticate the HCX API.
err = CloudAuthenticate(client, accessToken)
if err != nil {
return diag.FromErr(err)
return SDDC{}, diag.Errorf("failed to authenticate hcx api: %s", err.Error())
}

// Retrieve the SDDC details.
var sddc SDDC
if sddcID != "" {
sddc, err = GetSddcByID(client, sddcID)
} else {
sddc, err = GetSddcByName(client, sddcName)
}
if err != nil {
return diag.FromErr(err)
}

// Deactivate HCX
_, err = DeactivateHcxOnSDDC(client, sddc.ID)
if err != nil {
return diag.FromErr(err)
return SDDC{}, diag.Errorf("failed to retrieve sddc details: %s", err.Error())
}

// Wait for task to be completed
errcount := 0
for {
var sddc SDDC
if sddcID != "" {
sddc, err = GetSddcByID(client, sddcID)
} else {
sddc, err = GetSddcByName(client, sddcName)
}
if err != nil {
// Attempt to bypass recurring situation where the HCX API
// returns status 502 with a proxy server error, and an HTML response
// instead of JSON.
errcount++
hclog.Default().Info("[INFO] - resourceVmcDelete() - Error retrieving SDDC status: ", "error", err.Error(), "Errcount:", errcount)
if errcount > constants.VmcMaxRetries {
return diag.FromErr(err)
}
}

if sddc.DeploymentStatus == constants.VmcDeactivationInactiveStatus {
break
}

if sddc.DeploymentStatus == constants.VmcDeactivationFailedStatus {
return diag.Errorf("Deactivation failed")
}
return sddc, nil
}

time.Sleep(constants.VmcRetryInterval)
// calculateBackoff computes the exponential backoff duration with capped interval and jitter.
func calculateBackoff(retries int) time.Duration {
base := constants.VmcRetryInterval * time.Duration(1<<retries)
if base > constants.VmcMaxRetryInterval {
base = constants.VmcMaxRetryInterval
}
// Add jitter to reduce the chance of simultaneous retries across instances.
jitter := time.Duration(rand.Int63n(int64(base / 2)))
return base + jitter
}

return diags
// waitWithContext waits for the backoff duration or respects context cancellations.
func waitWithContext(ctx context.Context, backoff time.Duration) error {
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(backoff):
return nil
}
}

0 comments on commit d8bd705

Please sign in to comment.