From edf823b8bb26de8b916e7626566ad2747a0f4cb4 Mon Sep 17 00:00:00 2001 From: GraysonWu Date: Fri, 15 Sep 2023 13:45:36 -0700 Subject: [PATCH] failure domain support (#957) Signed-off-by: graysonwu --- nsxt/data_source_nsxt_failure_domain.go | 84 +++++++++ nsxt/provider.go | 2 + nsxt/resource_nsxt_failure_domain.go | 156 +++++++++++++++++ nsxt/resource_nsxt_failure_domain_test.go | 185 ++++++++++++++++++++ website/docs/d/failure_domain.html.markdown | 31 ++++ website/docs/r/failure_domain.html.markdown | 52 ++++++ 6 files changed, 510 insertions(+) create mode 100644 nsxt/data_source_nsxt_failure_domain.go create mode 100644 nsxt/resource_nsxt_failure_domain.go create mode 100644 nsxt/resource_nsxt_failure_domain_test.go create mode 100644 website/docs/d/failure_domain.html.markdown create mode 100644 website/docs/r/failure_domain.html.markdown diff --git a/nsxt/data_source_nsxt_failure_domain.go b/nsxt/data_source_nsxt_failure_domain.go new file mode 100644 index 000000000..2719ba94e --- /dev/null +++ b/nsxt/data_source_nsxt_failure_domain.go @@ -0,0 +1,84 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp/nsx" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp/nsx/model" +) + +func dataSourceNsxtFailureDomain() *schema.Resource { + return &schema.Resource{ + Read: dataSourceNsxtFailureDomainRead, + + Schema: map[string]*schema.Schema{ + "id": getDataSourceIDSchema(), + "display_name": getDataSourceDisplayNameSchema(), + "description": getDataSourceDescriptionSchema(), + }, + } +} + +func dataSourceNsxtFailureDomainRead(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + client := nsx.NewFailureDomainsClient(connector) + + objID := d.Get("id").(string) + objName := d.Get("display_name").(string) + var obj model.FailureDomain + if objID != "" { + // Get by id + objGet, err := client.Get(objID) + if isNotFoundError(err) { + return fmt.Errorf("FailureDomain with ID %s was not found", objID) + } + + if err != nil { + return fmt.Errorf("error while reading FailureDomain %s: %v", objID, err) + } + obj = objGet + } else if objName == "" { + return fmt.Errorf("error obtaining FailureDomain ID or name during read") + } else { + // Get by full name/prefix + objList, err := client.List() + if err != nil { + return fmt.Errorf("error while reading FailureDomains: %v", err) + } + // go over the list to find the correct one (prefer a perfect match. If not - prefix match) + var perfectMatch []model.FailureDomain + var prefixMatch []model.FailureDomain + for _, objInList := range objList.Results { + if strings.HasPrefix(*objInList.DisplayName, objName) { + prefixMatch = append(prefixMatch, objInList) + } + if *objInList.DisplayName == objName { + perfectMatch = append(perfectMatch, objInList) + } + } + if len(perfectMatch) > 0 { + if len(perfectMatch) > 1 { + return fmt.Errorf("found multiple FailureDomains with name '%s'", objName) + } + obj = perfectMatch[0] + } else if len(prefixMatch) > 0 { + if len(prefixMatch) > 1 { + return fmt.Errorf("found multiple FailureDomains with name starting with '%s'", objName) + } + obj = prefixMatch[0] + } else { + return fmt.Errorf("FailureDomain with name '%s' was not found", objName) + } + } + + d.SetId(*obj.Id) + d.Set("display_name", obj.DisplayName) + d.Set("description", obj.Description) + + return nil +} diff --git a/nsxt/provider.go b/nsxt/provider.go index d27dab571..e21ec6e4a 100644 --- a/nsxt/provider.go +++ b/nsxt/provider.go @@ -294,6 +294,7 @@ func Provider() *schema.Provider { "nsxt_uplink_host_switch_profile": dataSourceNsxtUplinkHostSwitchProfile(), "nsxt_compute_manager": dataSourceNsxtComputeManager(), "nsxt_transport_node_realization": dataSourceNsxtTransportNodeRealization(), + "nsxt_failure_domain": dataSourceNsxtFailureDomain(), }, ResourcesMap: map[string]*schema.Resource{ @@ -425,6 +426,7 @@ func Provider() *schema.Provider { "nsxt_manager_cluster": resourceNsxtManagerCluster(), "nsxt_uplink_host_switch_profile": resourceNsxtUplinkHostSwitchProfile(), "nsxt_transport_node": resourceNsxtTransportNode(), + "nsxt_failure_domain": resourceNsxtFailureDomain(), }, ConfigureFunc: providerConfigure, diff --git a/nsxt/resource_nsxt_failure_domain.go b/nsxt/resource_nsxt_failure_domain.go new file mode 100644 index 000000000..1c6380817 --- /dev/null +++ b/nsxt/resource_nsxt_failure_domain.go @@ -0,0 +1,156 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "log" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp/nsx" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp/nsx/model" +) + +var edgeServices = []string{ + "active", + "standby", + "no_preference", +} + +func resourceNsxtFailureDomain() *schema.Resource { + return &schema.Resource{ + Create: resourceNsxtFailureDomainCreate, + Read: resourceNsxtFailureDomainRead, + Update: resourceNsxtFailureDomainUpdate, + Delete: resourceNsxtFailureDomainDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "display_name": getDisplayNameSchema(), + "description": getDescriptionSchema(), + "revision": getRevisionSchema(), + "tag": getTagsSchema(), + "preferred_edge_services": { + Type: schema.TypeString, + Description: "Set preference for failure domain", + Optional: true, + Default: "no_preference", + ValidateFunc: validation.StringInSlice(edgeServices, false), + }, + }, + } +} + +func resourceNsxtFailureDomainRead(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("error obtaining FailureDomain ID") + } + + client := nsx.NewFailureDomainsClient(connector) + obj, err := client.Get(id) + if err != nil { + return fmt.Errorf("error during FailureDomain read: %v", err) + } + + d.Set("display_name", obj.DisplayName) + d.Set("description", obj.Description) + setMPTagsInSchema(d, obj.Tags) + d.Set("revision", obj.Revision) + + preferPtr := obj.PreferredActiveEdgeServices + preferStr := "no_preference" + if preferPtr != nil { + if *preferPtr == true { + preferStr = "active" + } else { + preferStr = "standby" + } + } + d.Set("preferred_edge_services", preferStr) + return nil +} + +func failureDomainSchemaToModel(d *schema.ResourceData) model.FailureDomain { + displayName := d.Get("display_name").(string) + description := d.Get("description").(string) + tags := getMPTagsFromSchema(d) + + obj := model.FailureDomain{ + DisplayName: &displayName, + Description: &description, + Tags: tags, + } + + preferredEdgeServices := d.Get("preferred_edge_services").(string) + if preferredEdgeServices != "no_preference" { + activePrefer := true + standbyPrefer := false + if preferredEdgeServices == "active" { + obj.PreferredActiveEdgeServices = &activePrefer + } else { + obj.PreferredActiveEdgeServices = &standbyPrefer + } + } + return obj +} + +func resourceNsxtFailureDomainCreate(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + client := nsx.NewFailureDomainsClient(connector) + + failureDomain := failureDomainSchemaToModel(d) + obj, err := client.Create(failureDomain) + if err != nil { + id := "" + if obj.Id != nil { + id = *obj.Id + } + return handleCreateError("Failure Domain", id, err) + } + log.Printf("[INFO] FailureDomain with ID %s created", *obj.Id) + d.SetId(*obj.Id) + + return resourceNsxtFailureDomainRead(d, m) +} + +func resourceNsxtFailureDomainUpdate(d *schema.ResourceData, m interface{}) error { + id := d.Id() + if id == "" { + return fmt.Errorf("error obtaining FailureDomain ID") + } + + connector := getPolicyConnector(m) + client := nsx.NewFailureDomainsClient(connector) + + failureDomain := failureDomainSchemaToModel(d) + revision := int64(d.Get("revision").(int)) + failureDomain.Revision = &revision + + _, err := client.Update(id, failureDomain) + if err != nil { + return handleCreateError("FailureDomain", id, err) + } + + return resourceNsxtFailureDomainRead(d, m) +} + +func resourceNsxtFailureDomainDelete(d *schema.ResourceData, m interface{}) error { + id := d.Id() + if id == "" { + return fmt.Errorf("error obtaining FailureDomain ID") + } + connector := getPolicyConnector(m) + client := nsx.NewFailureDomainsClient(connector) + err := client.Delete(id) + if err != nil { + return handleDeleteError("FailureDomain", id, err) + } + return nil +} diff --git a/nsxt/resource_nsxt_failure_domain_test.go b/nsxt/resource_nsxt_failure_domain_test.go new file mode 100644 index 000000000..37ce8d9e0 --- /dev/null +++ b/nsxt/resource_nsxt_failure_domain_test.go @@ -0,0 +1,185 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp/nsx" +) + +var accTestFailureDomainCreateAttributes = map[string]string{ + "display_name": getAccTestResourceName(), + "description": "terraform created", + "preferred_edge_services": "active", +} + +var accTestFailureDomainUpdateAttributes = map[string]string{ + "display_name": getAccTestResourceName(), + "description": "terraform created", + "preferred_edge_services": "standby", +} + +func TestAccResourceNsxtFailureDomain_basic(t *testing.T) { + testResourceName := "nsxt_failure_domain.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccOnlyLocalManager(t) + testAccPreCheck(t) + }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtFailureDomainCheckDestroy(state, accTestFailureDomainUpdateAttributes["display_name"]) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtFailureDomainTemplate(true), + Check: resource.ComposeTestCheckFunc( + testAccNsxtFailureDomainExists(accTestFailureDomainCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestFailureDomainCreateAttributes["display_name"]), + resource.TestCheckResourceAttr(testResourceName, "description", accTestFailureDomainCreateAttributes["description"]), + resource.TestCheckResourceAttr(testResourceName, "preferred_edge_services", accTestFailureDomainCreateAttributes["preferred_edge_services"]), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + ), + }, + { + Config: testAccNsxtFailureDomainTemplate(false), + Check: resource.ComposeTestCheckFunc( + testAccNsxtFailureDomainExists(accTestFailureDomainUpdateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestFailureDomainUpdateAttributes["display_name"]), + resource.TestCheckResourceAttr(testResourceName, "description", accTestFailureDomainUpdateAttributes["description"]), + resource.TestCheckResourceAttr(testResourceName, "preferred_edge_services", accTestFailureDomainUpdateAttributes["preferred_edge_services"]), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + ), + }, + { + Config: testAccNsxtFailureDomainMinimalistic(), + Check: resource.ComposeTestCheckFunc( + testAccNsxtFailureDomainExists(accTestFailureDomainCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "description", ""), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "0"), + ), + }, + }, + }) +} + +func TestAccResourceNsxtFailureDomain_importBasic(t *testing.T) { + testResourceName := "nsxt_failure_domain.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccOnlyLocalManager(t) + testAccPreCheck(t) + }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtFailureDomainCheckDestroy(state, accTestFailureDomainUpdateAttributes["display_name"]) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtFailureDomainMinimalistic(), + }, + { + ResourceName: testResourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccNsxtFailureDomainExists(displayName string, resourceName string) resource.TestCheckFunc { + return func(state *terraform.State) error { + + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + + rs, ok := state.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("FailureDomain resource %s not found in resources", resourceName) + } + + resourceID := rs.Primary.ID + if resourceID == "" { + return fmt.Errorf("FailureDomain resource ID not set in resources") + } + + client := nsx.NewFailureDomainsClient(connector) + _, err := client.Get(resourceID) + + if isNotFoundError(err) { + return fmt.Errorf("FailureDomain %s does not exist", resourceID) + } + + if err != nil { + return fmt.Errorf("error while retrieving Failure Domain ID %s, error: %v", resourceID, err) + } + + return nil + } +} + +func testAccNsxtFailureDomainCheckDestroy(state *terraform.State, displayName string) error { + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + for _, rs := range state.RootModule().Resources { + if rs.Type != "nsxt_failure_domain" { + continue + } + + resourceID := rs.Primary.Attributes["id"] + client := nsx.NewFailureDomainsClient(connector) + obj, err := client.Get(resourceID) + + if isNotFoundError(err) { + return nil + } + + if err != nil { + return fmt.Errorf("error while retrieving Failure Domain ID %s. Error: %v", resourceID, err) + } + + if obj.DisplayName != nil && displayName == *obj.DisplayName { + return fmt.Errorf("FailureDomain %s still exists", displayName) + } + } + return nil +} + +func testAccNsxtFailureDomainTemplate(createFlow bool) string { + var attrMap map[string]string + if createFlow { + attrMap = accTestFailureDomainCreateAttributes + } else { + attrMap = accTestFailureDomainUpdateAttributes + } + return fmt.Sprintf(` +resource "nsxt_failure_domain" "test" { + display_name = "%s" + description = "%s" + preferred_edge_services = "%s" + tag { + scope = "scope1" + tag = "tag1" + } +} + +data "nsxt_failure_domain" "test" { + display_name = "%s" + depends_on = [nsxt_failure_domain.test] +}`, attrMap["display_name"], attrMap["description"], attrMap["preferred_edge_services"], attrMap["display_name"]) +} + +func testAccNsxtFailureDomainMinimalistic() string { + return fmt.Sprintf(` +resource "nsxt_failure_domain" "test" { + display_name = "%s" +}`, accTestUplinkHostSwitchProfileUpdateAttributes["display_name"]) +} diff --git a/website/docs/d/failure_domain.html.markdown b/website/docs/d/failure_domain.html.markdown new file mode 100644 index 000000000..4693556a6 --- /dev/null +++ b/website/docs/d/failure_domain.html.markdown @@ -0,0 +1,31 @@ +--- +subcategory: "Fabric" +layout: "nsxt" +page_title: "NSXT: failure_domain" +description: Failure Domain data source. +--- + +# nsxt_failure_domain + +This data source provides information about Failure Domain configured in NSX. + +This resource is supported with NSX 3.0.0 onwards. + +## Example Usage + +```hcl +data "nsxt_failure_domain" "failure_domain" { + display_name = "failuredomain1" +} +``` + +## Argument Reference + +* `id` - (Optional) The ID of Failure Domain to retrieve. +* `display_name` - (Optional) The Display Name prefix of the Failure Domain to retrieve. + +## Attributes Reference + +In addition to arguments listed above, the following attributes are exported: + +* `description` - The description of the resource. diff --git a/website/docs/r/failure_domain.html.markdown b/website/docs/r/failure_domain.html.markdown new file mode 100644 index 000000000..e9fc59bdf --- /dev/null +++ b/website/docs/r/failure_domain.html.markdown @@ -0,0 +1,52 @@ +--- +subcategory: "Fabric" +layout: "nsxt" +page_title: "NSXT: nsxt_failure_domain" +description: A resource to configure a Failure Domain. +--- + +# nsxt_failure_domain + +This resource provides a method for the management of a Failure Domain. + +This resource is supported with NSX 3.0.0 onwards. + +## Example Usage + +```hcl +data "nsxt_failure_domain" "failure_domain" { + display_name = "failuredomain1" + description = "failuredomain" + preferred_edge_services = "active" + tag { + scope = "scope1" + tag = "tag1" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `display_name` - (Required) Display name of the resource. +* `description` - (Optional) Description of the resource. +* `preferred_edge_services` - Set preference for failure domain. Set preference for edge transport node failure domain which will be considered while doing auto placement of logical router, DHCP and MDProxy on edge node. `active`: For preemptive failover mode, active edge cluster member allocation prefers this failure domain. `standby`: For preemptive failover mode, standby edge cluster member allocation prefers this failure domain. Default will be `no_preference`. It means no explicit preference. + +## Attributes Reference + +In addition to arguments listed above, the following attributes are exported: + +* `id` - ID of the resource. +* `revision` - Indicates current revision number of the object as seen by NSX-T API server. This attribute can be useful for debugging. + +## Importing + +An existing Failure Domain can be [imported][docs-import] into this resource, via the following command: + +[docs-import]: https://www.terraform.io/cli/import + +``` +terraform import nsxt_failure_domain.failure_domain UUID +``` +The above command imports Failure Domain named `test` with the NSX Failure Domain ID `UUID`.