From e5df2d267c1f99e2f963d2747daeffb7490ebff4 Mon Sep 17 00:00:00 2001 From: Shizhao Liu Date: Wed, 13 Dec 2023 22:51:43 +0000 Subject: [PATCH] Resource to prepare for NSXT cluster upgrade Signed-off-by: Shizhao Liu --- nsxt/provider.go | 2 + ...ource_nsxt_upgrade_precheck_acknowledge.go | 96 +++++ nsxt/resource_nsxt_upgrade_prepare.go | 378 ++++++++++++++++++ ...upgrade_precheck_acknowledge.html.markdown | 29 ++ website/docs/r/upgrade_prepare.html.markdown | 47 +++ 5 files changed, 552 insertions(+) create mode 100644 nsxt/resource_nsxt_upgrade_precheck_acknowledge.go create mode 100644 nsxt/resource_nsxt_upgrade_prepare.go create mode 100644 website/docs/r/upgrade_precheck_acknowledge.html.markdown create mode 100644 website/docs/r/upgrade_prepare.html.markdown diff --git a/nsxt/provider.go b/nsxt/provider.go index cd8ca7e96..1a00da0cc 100644 --- a/nsxt/provider.go +++ b/nsxt/provider.go @@ -467,6 +467,8 @@ func Provider() *schema.Provider { "nsxt_policy_lb_passive_monitor_profile": resourceNsxtPolicyLBPassiveMonitorProfile(), "nsxt_policy_lb_tcp_monitor_profile": resourceNsxtPolicyLBTcpMonitorProfile(), "nsxt_policy_lb_udp_monitor_profile": resourceNsxtPolicyLBUdpMonitorProfile(), + "nsxt_upgrade_prepare": resourceNsxtUpgradePrepare(), + "nsxt_upgrade_precheck_acknowledge": resourceNsxtUpgradePrecheckAcknowledge(), }, ConfigureFunc: providerConfigure, diff --git a/nsxt/resource_nsxt_upgrade_precheck_acknowledge.go b/nsxt/resource_nsxt_upgrade_precheck_acknowledge.go new file mode 100644 index 000000000..19716e384 --- /dev/null +++ b/nsxt/resource_nsxt_upgrade_precheck_acknowledge.go @@ -0,0 +1,96 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + nsxModel "github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp/nsx/model" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp/nsx/upgrade" +) + +func resourceNsxtUpgradePrecheckAcknowledge() *schema.Resource { + return &schema.Resource{ + Create: resourceNsxtUpgradePrecheckAcknowledgeCreate, + Read: resourceNsxtUpgradePrecheckAcknowledgeRead, + Update: resourceNsxtUpgradePrecheckAcknowledgeUpdate, + Delete: resourceNsxtUpgradePrecheckAcknowledgeDelete, + + Schema: map[string]*schema.Schema{ + "precheck_ids": { + Type: schema.TypeList, + Description: "IDs of precheck warnings that need to be acknowledged", + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Required: true, + }, + }, + } +} + +func resourceNsxtUpgradePrecheckAcknowledgeCreate(d *schema.ResourceData, m interface{}) error { + id := d.Id() + if id == "" { + id = newUUID() + } + d.SetId(id) + precheckIDs := interface2StringList(d.Get("precheck_ids").([]interface{})) + err := acknowledgePrecheckWarnings(m, precheckIDs) + if err != nil { + return handleCreateError("NsxtPrecheckAcknowledge", id, err) + } + return resourceNsxtUpgradePrecheckAcknowledgeRead(d, m) +} + +func acknowledgePrecheckWarnings(m interface{}, precheckIDs []string) error { + connector := getPolicyConnector(m) + client := upgrade.NewPreUpgradeChecksClient(connector) + for _, precheckID := range precheckIDs { + err := client.Acknowledge(precheckID) + if err != nil { + return fmt.Errorf("Failed to acknowledge precheck warning with ID %s: %s", precheckID, err) + } + } + return nil +} + +func resourceNsxtUpgradePrecheckAcknowledgeRead(d *schema.ResourceData, m interface{}) error { + id := d.Id() + precheckWarnings, err := getPrecheckErrors(m, nsxModel.UpgradeCheckFailure_TYPE_WARNING) + if err != nil { + return handleReadError(d, "NsxtUpgradePrecheckAcknowledge", id, err) + } + err = setAcknowledgedPrecheckIDsInSchema(d, precheckWarnings) + if err != nil { + return handleReadError(d, "NsxtUpgradePrecheckAcknowledge", id, err) + } + return nil +} + +func setAcknowledgedPrecheckIDsInSchema(d *schema.ResourceData, precheckWarnings []nsxModel.UpgradeCheckFailure) error { + var precheckWarningIDs []string + for _, precheckWarning := range precheckWarnings { + if !(*precheckWarning.NeedsAck) { + id := *precheckWarning.Id + precheckWarningIDs = append(precheckWarningIDs, id) + } + } + return d.Set("precheck_ids", precheckWarningIDs) +} + +func resourceNsxtUpgradePrecheckAcknowledgeUpdate(d *schema.ResourceData, m interface{}) error { + id := d.Id() + precheckIDs := interface2StringList(d.Get("precheck_ids").([]interface{})) + err := acknowledgePrecheckWarnings(m, precheckIDs) + if err != nil { + return handleUpdateError("NsxtPrecheckAcknowledge", id, err) + } + return resourceNsxtUpgradePrecheckAcknowledgeRead(d, m) +} + +func resourceNsxtUpgradePrecheckAcknowledgeDelete(d *schema.ResourceData, m interface{}) error { + return nil +} diff --git a/nsxt/resource_nsxt_upgrade_prepare.go b/nsxt/resource_nsxt_upgrade_prepare.go new file mode 100644 index 000000000..0ec9c35b8 --- /dev/null +++ b/nsxt/resource_nsxt_upgrade_prepare.go @@ -0,0 +1,378 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp/nsx" + nsxModel "github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp/nsx/model" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp/nsx/upgrade" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp/nsx/upgrade/bundles" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp/nsx/upgrade/eula" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp/nsx/upgrade/pre_upgrade_checks" +) + +var precheckComponentTypes = []string{"EDGE", "HOST", "MP"} + +func resourceNsxtUpgradePrepare() *schema.Resource { + return &schema.Resource{ + Create: resourceNsxtUpgradePrepareCreate, + Read: resourceNsxtUpgradePrepareRead, + Update: resourceNsxtUpgradePrepareUpdate, + Delete: resourceNsxtUpgradePrepareDelete, + + Schema: map[string]*schema.Schema{ + "upgrade_bundle_url": { + Type: schema.TypeString, + Description: "URL of the NSXT Upgrade bundle", + Required: true, + }, + "precheck_bundle_url": { + Type: schema.TypeString, + Description: "URL of the NSXT Upgrade precheck bundle (Only applied to NSXT version >= 4.1.1)", + Optional: true, + }, + "version": { + Type: schema.TypeString, + Description: "Version for the upgrade", + Required: true, + }, + "accept_user_agreement": { + Type: schema.TypeBool, + Description: "Whether to accept the user agreement", + Required: true, + }, + "failed_prechecks": { + Type: schema.TypeList, + Description: "List of failed prechecks for the upgrade, only include warnings, if precheck failed with error then the resource creation will fail", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Description: "ID of failed precheck", + Computed: true, + }, + "message": { + Type: schema.TypeString, + Description: "Message of the failed precheck", + Computed: true, + }, + }, + }, + Computed: true, + }, + }, + } +} + +func resourceNsxtUpgradePrepareCreate(d *schema.ResourceData, m interface{}) error { + id := d.Id() + if id == "" { + id = newUUID() + } + err := prepareForUpgrade(d, m) + if err != nil { + return handleCreateError("NsxtUpgradePrepare", id, err) + } + d.SetId(id) + return resourceNsxtUpgradePrepareRead(d, m) +} + +func prepareForUpgrade(d *schema.ResourceData, m interface{}) error { + // 0. Accept user agreement + err := acceptUserAgreement(d, m) + if err != nil { + return err + } + // 1. Upload upgrade bundle and wait for upload to complete + err = uploadPrecheckAndUpgradeBundle(d, m) + if err != nil { + return logAPIError("Failed to upload bundle", err) + } + // 2. Upgrade UC and check for its upgrade status + err = upgradeUc(d, m) + if err != nil { + return logAPIError("Failed to upgrade Upgrade Coordinator", err) + } + // 3. Execute pre-upgrade checks + err = executePreupgradeChecks(d, m) + if err != nil { + return logAPIError("Failed to execute pre-upgrade checks", err) + } + // 4. Check if there is error in prechecks + precheckFailures, err := getPrecheckErrors(m, nsxModel.UpgradeCheckFailure_TYPE_FAILURE) + if err != nil { + return logAPIError("Failed to get precheck errors", err) + } + if len(precheckFailures) > 0 { + var failureIDs []string + for _, failure := range precheckFailures { + failureID := *failure.Id + failureIDs = append(failureIDs, failureID) + } + return fmt.Errorf("Encounter errors in precheck %s, please address these error on NSX UI", failureIDs) + } + return nil +} + +func resourceNsxtUpgradePrepareRead(d *schema.ResourceData, m interface{}) error { + id := d.Id() + precheckWarnings, err := getPrecheckErrors(m, nsxModel.UpgradeCheckFailure_TYPE_WARNING) + if err != nil { + return handleReadError(d, "NsxtUpgradePrepare", id, err) + } + err = setFailedPrechecksInSchema(d, precheckWarnings) + if err != nil { + return handleReadError(d, "NsxtUpgradePrepare", id, err) + } + return nil +} + +func resourceNsxtUpgradePrepareUpdate(d *schema.ResourceData, m interface{}) error { + id := d.Id() + err := prepareForUpgrade(d, m) + if err != nil { + return handleUpdateError("NsxtUpgradePrepare", id, err) + } + return resourceNsxtUpgradePrepareRead(d, m) +} + +func resourceNsxtUpgradePrepareDelete(d *schema.ResourceData, m interface{}) error { + return nil +} + +func uploadPrecheckAndUpgradeBundle(d *schema.ResourceData, m interface{}) error { + upgradeBundleURL := d.Get("upgrade_bundle_url").(string) + precheckBundleURL := d.Get("precheck_bundle_url").(string) + upgradeBundleType := nsxModel.UpgradeBundleFetchRequest_BUNDLE_TYPE_UPGRADE + precheckBundleType := nsxModel.UpgradeBundleFetchRequest_BUNDLE_TYPE_PRE_UPGRADE + version := d.Get("version").(string) + c := m.(nsxtClients) + userName := c.NsxtClientConfig.UserName + password := c.NsxtClientConfig.Password + if !precheckBundleCompatibilityCheck(precheckBundleURL) { + return fmt.Errorf("Precheck bundle is only supported and is required for NSXT version >= 4.1.1") + } + if len(precheckBundleURL) > 0 { + err := uploadUpgradeBundle(m, precheckBundleURL, userName, password, precheckBundleType, version) + if err != nil { + return fmt.Errorf("Failed to upload precheck bundle: %s", err) + } + } + err := uploadUpgradeBundle(m, upgradeBundleURL, userName, password, upgradeBundleType, version) + if err != nil { + return fmt.Errorf("Failed to upload upgrade bundle: %s", err) + } + return nil +} + +func precheckBundleCompatibilityCheck(precheckBundleURL string) bool { + if nsxVersionLower("4.1.1") && len(precheckBundleURL) > 0 { + return false + } + if nsxVersionHigherOrEqual("4.1.1") && len(precheckBundleURL) == 0 { + return false + } + return true +} + +func uploadUpgradeBundle(m interface{}, url string, userName string, password string, bundleType string, version string) error { + connector := getPolicyConnector(m) + client := upgrade.NewBundlesClient(connector) + bundleFetchRequest := nsxModel.UpgradeBundleFetchRequest{ + Url: &url, + } + if nsxVersionHigherOrEqual("4.1.1") { + bundleFetchRequest.BundleType = &bundleType + bundleFetchRequest.Password = &password + bundleFetchRequest.Username = &userName + bundleFetchRequest.Version = &version + } + bundleID, err := client.Create(bundleFetchRequest, nil) + if err != nil { + return fmt.Errorf("Failed to upload upgrade bundle of type %s: %v", bundleType, err) + } + return waitForBundleUpload(m, *bundleID.BundleId) +} + +func acceptUserAgreement(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + acceptUserAgreement := d.Get("accept_user_agreement").(bool) + if !acceptUserAgreement { + return fmt.Errorf("To proceed with upgrade, you must accept user agreement") + } + client := eula.NewAcceptClient(connector) + err := client.Create() + if err != nil { + return err + } + return nil +} + +func upgradeUc(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + client := nsx.NewUpgradeClient(connector) + err := client.Upgradeuc() + if err != nil { + return err + } + return waitForUcUpgrade(m) +} + +func executePreupgradeChecks(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + client := nsx.NewUpgradeClient(connector) + err := client.Executepreupgradechecks(nil, nil, nil, nil, nil, nil) + if err != nil { + return err + } + for _, componentType := range precheckComponentTypes { + err = waitForPrecheckComplete(m, componentType) + if err != nil { + return err + } + } + return nil +} + +func getPrecheckErrors(m interface{}, typeParam string) ([]nsxModel.UpgradeCheckFailure, error) { + connector := getPolicyConnector(m) + client := pre_upgrade_checks.NewFailuresClient(connector) + resultList, err := client.List(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, &typeParam, nil, nil) + if err != nil { + return nil, err + } + return resultList.Results, nil +} + +func setFailedPrechecksInSchema(d *schema.ResourceData, precheckWarnings []nsxModel.UpgradeCheckFailure) error { + var failedPrechecksList []map[string]interface{} + for _, warning := range precheckWarnings { + if *warning.NeedsAck { + id := *warning.Id + message := *(warning.Message.Message) + elem := make(map[string]interface{}) + elem["id"] = id + elem["message"] = message + failedPrechecksList = append(failedPrechecksList, elem) + } + } + return d.Set("failed_prechecks", failedPrechecksList) +} + +func waitForBundleUpload(m interface{}, bundleID string) error { + connector := getPolicyConnector(m) + client := bundles.NewUploadStatusClient(connector) + pendingStates := []string{ + nsxModel.UpgradeBundleUploadStatus_STATUS_UPLOADING, + nsxModel.UpgradeBundleUploadStatus_STATUS_VERIFYING, + } + targetStates := []string{ + nsxModel.UpgradeBundleUploadStatus_STATUS_SUCCESS, + nsxModel.UpgradeBundleUploadStatus_STATUS_FAILED, + } + timeout := 1200 + stateConf := &resource.StateChangeConf{ + Pending: pendingStates, + Target: targetStates, + Refresh: func() (interface{}, string, error) { + state, err := client.Get(bundleID) + if err != nil { + msg := fmt.Sprintf("Error while retrieving upload status of bundle %s", bundleID) + return state, nsxModel.UpgradeBundleUploadStatus_STATUS_FAILED, logAPIError(msg, err) + } + + log.Printf("[DEBUG] Current status for uploading bundle %s is %s", bundleID, *state.Status) + + return state, *state.Status, nil + }, + Timeout: time.Duration(timeout) * time.Second, + MinTimeout: 1 * time.Second, + Delay: 1 * time.Second, + } + _, err := stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Failed to upload bundle %s: %s", bundleID, err) + } + return nil +} + +func waitForUcUpgrade(m interface{}) error { + connector := getPolicyConnector(m) + client := upgrade.NewUcUpgradeStatusClient(connector) + pendingStates := []string{ + nsxModel.UcUpgradeStatus_STATE_NOT_STARTED, + nsxModel.UcUpgradeStatus_STATE_IN_PROGRESS, + } + targetStates := []string{ + nsxModel.UcUpgradeStatus_STATE_SUCCESS, + nsxModel.UcUpgradeStatus_STATE_FAILED, + } + timeout := 1200 + stateConf := &resource.StateChangeConf{ + Pending: pendingStates, + Target: targetStates, + Refresh: func() (interface{}, string, error) { + state, err := client.Get() + if err != nil { + return state, nsxModel.UcUpgradeStatus_STATE_FAILED, logAPIError("Error while retrieving UC upgrade status", err) + } + + log.Printf("[DEBUG] Current status for UC Upgrade is %s", *state.State) + + return state, *state.State, nil + }, + Timeout: time.Duration(timeout) * time.Second, + MinTimeout: 1 * time.Second, + Delay: 1 * time.Second, + } + _, err := stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Failed to upgrade UC: %s", err) + } + return nil +} + +func waitForPrecheckComplete(m interface{}, componentType string) error { + connector := getPolicyConnector(m) + client := upgrade.NewStatusSummaryClient(connector) + pendingStates := []string{ + nsxModel.UpgradeChecksExecutionStatus_STATUS_NOT_STARTED, + nsxModel.UpgradeChecksExecutionStatus_STATUS_IN_PROGRESS, + nsxModel.UpgradeChecksExecutionStatus_STATUS_ABORTING, + } + targetStates := []string{ + nsxModel.UpgradeChecksExecutionStatus_STATUS_ABORTED, + nsxModel.UpgradeChecksExecutionStatus_STATUS_COMPLETED, + } + timeout := 1200 + stateConf := &resource.StateChangeConf{ + Pending: pendingStates, + Target: targetStates, + Refresh: func() (interface{}, string, error) { + state, err := client.Get(&componentType, nil, nil) + if err != nil { + return state, nsxModel.UpgradeChecksExecutionStatus_STATUS_ABORTED, logAPIError("Error retrieving component upgrade status", err) + } + componentStatus := state.ComponentStatus + if len(componentStatus) != 1 { + return state, nsxModel.UpgradeChecksExecutionStatus_STATUS_ABORTED, logAPIError("Error retrieving component upgrade status", err) + } + return state, *componentStatus[0].PreUpgradeStatus.Status, nil + }, + Timeout: time.Duration(timeout) * time.Second, + MinTimeout: 1 * time.Second, + Delay: 1 * time.Second, + } + _, err := stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Encounter error while running precheck on component type %s: %s", componentType, err) + } + return nil +} diff --git a/website/docs/r/upgrade_precheck_acknowledge.html.markdown b/website/docs/r/upgrade_precheck_acknowledge.html.markdown new file mode 100644 index 000000000..3f518e2ec --- /dev/null +++ b/website/docs/r/upgrade_precheck_acknowledge.html.markdown @@ -0,0 +1,29 @@ +--- +subcategory: "Beta" +layout: "nsxt" +page_title: "NSXT: nsxt_upgrade_precheck_acknowledge" +description: A resource to acknowledge failed NSXT upgrade prechecks. +--- + +# nsxt_upgrade_precheck_acknowledge + +This resource provides a method for acknowledging the failed prechecks +for NSXT upgrade. + +## Example Usage + +```hcl +resource "nsxt_upgrade_precheck_acknowledge" "test" { + precheck_ids = ["backupOperationCheck", "pUBCheck"] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `precheck_ids` - (Required) List of ids of failed prechecks user want to acknowledge. + +## Importing + +Importing is not supported for this resource. diff --git a/website/docs/r/upgrade_prepare.html.markdown b/website/docs/r/upgrade_prepare.html.markdown new file mode 100644 index 000000000..74dfba7bc --- /dev/null +++ b/website/docs/r/upgrade_prepare.html.markdown @@ -0,0 +1,47 @@ +--- +subcategory: "Beta" +layout: "nsxt" +page_title: "NSXT: nsxt_upgrade_prepare" +description: A resource to prepare for the upgrade of NSXT cluster. +--- + +# nsxt_upgrade_prepare + +This resource provides a method for preparing the upgrade of NSXT cluster, +it will upload upgrade bundle to NSX and run upgrade prechecks. +If there are errors in precheck result, the creation of resource will fail. +Precheck warnings will be listed in the `failed_prechecks` field of terraform state, +user can use the nsxt_upgrade_precheck_acknowledge resource to acknowledge these +warnings. + +## Example Usage + +```hcl +resource "nsxt_upgrade_prepare" "test" { + upgrade_bundle_url = "http://url-to-upgrade-bundle.mub" + precheck_bundle_url = "http://url-to-precheck-bundle.pub" + version = "4.2.0" + accept_user_agreement = true +} +``` + +## Argument Reference + +The following arguments are supported: + +* `upgrade_bundle_url` - (Required) The url to download the Manager Upgrade bundle. +* `precheck_bundle_url` - (Optional) The url to download the Precheck bundle. This field is required and only applied to NSX version >= 4.1.1 +* `accept_user_agreement` - (Required) User agreement must be accepted before upgrade. +* `version` - (Required) Target version of the upgrade. + +## Argument Reference + +In addition to arguments listed above, the following attributes are exported: + +* `failed_prechecks` - (Computed) Failed prechecks from running pre-upgrade check. + * `id` - ID of the failed precheck. + * `message` - Message of the failed precheck. + +## Importing + +Importing is not supported for this resource.