From fd709bea9d56962262e9d8f2ee9bcccf7bf251a4 Mon Sep 17 00:00:00 2001 From: Kobi Samoray Date: Tue, 3 Oct 2023 11:35:44 +0200 Subject: [PATCH] Implement edge high availability profile resource Signed-off-by: Kobi Samoray --- nsxt/provider.go | 1 + ...rce_nsxt_edge_high_availability_profile.go | 195 ++++++++++++++++++ ...sxt_edge_high_availability_profile_test.go | 158 ++++++++++++++ ...ge_high_availability_profile.html.markdown | 57 +++++ 4 files changed, 411 insertions(+) create mode 100644 nsxt/resource_nsxt_edge_high_availability_profile.go create mode 100644 nsxt/resource_nsxt_edge_high_availability_profile_test.go create mode 100644 website/docs/r/edge_high_availability_profile.html.markdown diff --git a/nsxt/provider.go b/nsxt/provider.go index bceafb15c..c5fccf275 100644 --- a/nsxt/provider.go +++ b/nsxt/provider.go @@ -432,6 +432,7 @@ func Provider() *schema.Provider { "nsxt_cluster_virtual_ip": resourceNsxtClusterVirualIP(), "nsxt_policy_host_transport_node_profile": resourceNsxtPolicyHostTransportNodeProfile(), "nsxt_policy_host_transport_node": resourceNsxtPolicyHostTransportNode(), + "nsxt_edge_high_availability_profile": resourceNsxtEdgeHighAvailabilityProfile(), }, ConfigureFunc: providerConfigure, diff --git a/nsxt/resource_nsxt_edge_high_availability_profile.go b/nsxt/resource_nsxt_edge_high_availability_profile.go new file mode 100644 index 000000000..03006f5cf --- /dev/null +++ b/nsxt/resource_nsxt_edge_high_availability_profile.go @@ -0,0 +1,195 @@ +/* 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" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/vmware/vsphere-automation-sdk-go/runtime/bindings" + "github.com/vmware/vsphere-automation-sdk-go/runtime/data" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp/nsx" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp/nsx/model" +) + +func resourceNsxtEdgeHighAvailabilityProfile() *schema.Resource { + return &schema.Resource{ + Create: resourceNsxtEdgeHighAvailabilityProfileCreate, + Read: resourceNsxtEdgeHighAvailabilityProfileRead, + Update: resourceNsxtEdgeHighAvailabilityProfileUpdate, + Delete: resourceNsxtEdgeHighAvailabilityProfileDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Schema: map[string]*schema.Schema{ + "revision": getRevisionSchema(), + "description": getDescriptionSchema(), + "display_name": getDisplayNameSchema(), + "tag": getTagsSchema(), + "bfd_allowed_hops": { + Type: schema.TypeInt, + Optional: true, + Default: 255, + Description: "BFD allowed hops", + ValidateFunc: validation.IntBetween(1, 255), + }, + "bfd_declare_dead_multiple": { + Type: schema.TypeInt, + Optional: true, + Default: 3, + Description: "Number of times a packet is missed before BFD declares the neighbor down", + ValidateFunc: validation.IntBetween(2, 16), + }, + "bfd_probe_interval": { + Type: schema.TypeInt, + Optional: true, + Default: 500, + Description: "the time interval (in millisecond) between probe packets for heartbeat purpose", + ValidateFunc: validation.IntBetween(50, 60000), + }, + "standby_relocation_threshold": { + Type: schema.TypeInt, + Optional: true, + Default: 30, + Description: "Standby service context relocation wait time", + ValidateFunc: validation.IntBetween(10, 20000), + }, + }, + } +} + +func resourceNsxtEdgeHighAvailabilityProfileCreate(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + client := nsx.NewClusterProfilesClient(connector) + description := d.Get("description").(string) + displayName := d.Get("display_name").(string) + tags := getMPTagsFromSchema(d) + bfdAllowedHops := int64(d.Get("bfd_allowed_hops").(int)) + bfdDeclareDeadMultiple := int64(d.Get("bfd_declare_dead_multiple").(int)) + bfdProbeInterval := int64(d.Get("bfd_probe_interval").(int)) + standbyRelocationThreshold := int64(d.Get("standby_relocation_threshold").(int)) + + obj := model.EdgeHighAvailabilityProfile{ + Description: &description, + DisplayName: &displayName, + Tags: tags, + BfdAllowedHops: &bfdAllowedHops, + BfdDeclareDeadMultiple: &bfdDeclareDeadMultiple, + BfdProbeInterval: &bfdProbeInterval, + StandbyRelocationConfig: &model.StandbyRelocationConfig{ + StandbyRelocationThreshold: &standbyRelocationThreshold, + }, + ResourceType: model.EdgeHighAvailabilityProfile__TYPE_IDENTIFIER, + } + converter := bindings.NewTypeConverter() + dataValue, errs := converter.ConvertToVapi(obj, model.EdgeHighAvailabilityProfileBindingType()) + if errs != nil { + return errs[0] + } + + structValue, err := client.Create(dataValue.(*data.StructValue)) + if err != nil { + return handleCreateError("Edge High Availability Profile", displayName, err) + } + o, errs := converter.ConvertToGolang(structValue, model.EdgeHighAvailabilityProfileBindingType()) + if errs != nil { + return errs[0] + } + obj = o.(model.EdgeHighAvailabilityProfile) + + d.SetId(*obj.Id) + return resourceNsxtEdgeHighAvailabilityProfileRead(d, m) +} + +func resourceNsxtEdgeHighAvailabilityProfileRead(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + id := d.Id() + if id == "" { + return fmt.Errorf("error obtaining logical object id") + } + client := nsx.NewClusterProfilesClient(connector) + structValue, err := client.Get(id) + if err != nil { + return handleReadError(d, "Edge High Availability Profile", id, err) + } + converter := bindings.NewTypeConverter() + o, errs := converter.ConvertToGolang(structValue, model.EdgeHighAvailabilityProfileBindingType()) + if errs != nil { + return errs[0] + } + obj := o.(model.EdgeHighAvailabilityProfile) + d.Set("revision", obj.Revision) + d.Set("description", obj.Description) + d.Set("display_name", obj.DisplayName) + setMPTagsInSchema(d, obj.Tags) + d.Set("bfd_allowed_hops", obj.BfdAllowedHops) + d.Set("bfd_declare_dead_multiple", obj.BfdDeclareDeadMultiple) + d.Set("bfd_probe_interval", obj.BfdProbeInterval) + if obj.StandbyRelocationConfig != nil { + d.Set("standby_relocation_threshold", obj.StandbyRelocationConfig.StandbyRelocationThreshold) + } + + return nil +} + +func resourceNsxtEdgeHighAvailabilityProfileUpdate(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + id := d.Id() + if id == "" { + return fmt.Errorf("error obtaining logical object id") + } + client := nsx.NewClusterProfilesClient(connector) + revision := int64(d.Get("revision").(int)) + description := d.Get("description").(string) + displayName := d.Get("display_name").(string) + tags := getMPTagsFromSchema(d) + bfdAllowedHops := int64(d.Get("bfd_allowed_hops").(int)) + bfdDeclareDeadMultiple := int64(d.Get("bfd_declare_dead_multiple").(int)) + bfdProbeInterval := int64(d.Get("bfd_probe_interval").(int)) + standbyRelocationThreshold := int64(d.Get("standby_relocation_threshold").(int)) + + obj := model.EdgeHighAvailabilityProfile{ + Revision: &revision, + Description: &description, + DisplayName: &displayName, + Tags: tags, + BfdAllowedHops: &bfdAllowedHops, + BfdDeclareDeadMultiple: &bfdDeclareDeadMultiple, + BfdProbeInterval: &bfdProbeInterval, + StandbyRelocationConfig: &model.StandbyRelocationConfig{ + StandbyRelocationThreshold: &standbyRelocationThreshold, + }, + ResourceType: model.EdgeHighAvailabilityProfile__TYPE_IDENTIFIER, + } + converter := bindings.NewTypeConverter() + dataValue, errs := converter.ConvertToVapi(obj, model.EdgeHighAvailabilityProfileBindingType()) + if errs != nil { + return errs[0] + } + + _, err := client.Update(id, dataValue.(*data.StructValue)) + if err != nil { + return handleUpdateError("Edge High Availability Profile", id, err) + } + + return resourceNsxtEdgeHighAvailabilityProfileRead(d, m) +} + +func resourceNsxtEdgeHighAvailabilityProfileDelete(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("error obtaining logical object id") + } + + client := nsx.NewClusterProfilesClient(connector) + + err := client.Delete(id) + if err != nil { + return handleDeleteError("Edge High Availability Profile", id, err) + } + return nil +} diff --git a/nsxt/resource_nsxt_edge_high_availability_profile_test.go b/nsxt/resource_nsxt_edge_high_availability_profile_test.go new file mode 100644 index 000000000..0a70d9ab8 --- /dev/null +++ b/nsxt/resource_nsxt_edge_high_availability_profile_test.go @@ -0,0 +1,158 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "testing" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/vmware/vsphere-automation-sdk-go/runtime/bindings" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp/nsx" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp/nsx/model" +) + +func TestAccResourceNsxtEdgeHighAvailabilityProfile_basic(t *testing.T) { + profileName := getAccTestResourceName() + updateProfileName := "updated-" + profileName + testResourceName := "nsxt_edge_high_availability_profile.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccOnlyLocalManager(t); testAccTestFabric(t); testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNSXEdgeHighAvailabilityProfileCheckDestroy(state, updateProfileName) + }, + + Steps: []resource.TestStep{ + { + Config: testAccNSXEdgeHighAvailabilityProfileCreateTemplate(profileName), + Check: resource.ComposeTestCheckFunc( + testAccNSXEdgeHighAvailabilityProfileExists(profileName, testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", profileName), + resource.TestCheckResourceAttr(testResourceName, "description", "Terraform test edge high availability profile"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + ), + }, + { + Config: testAccNSXEdgeHighAvailabilityProfileCreateTemplate(updateProfileName), + Check: resource.ComposeTestCheckFunc( + testAccNSXEdgeHighAvailabilityProfileExists(updateProfileName, testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", updateProfileName), + resource.TestCheckResourceAttr(testResourceName, "description", "Terraform test edge high availability profile"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + ), + }, + }, + }) +} + +func TestAccResourceNsxtEdgeHighAvailabilityProfile_importBasic(t *testing.T) { + name := getAccTestResourceName() + profileName := getAccTestResourceName() + testResourceName := "nsxt_edge_high_availability_profile.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccOnlyLocalManager(t); testAccTestFabric(t); testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNSXEdgeHighAvailabilityProfileCheckDestroy(state, name) + }, + Steps: []resource.TestStep{ + { + Config: testAccNSXEdgeHighAvailabilityProfileCreateTemplate(profileName), + }, + { + ResourceName: testResourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccNSXEdgeHighAvailabilityProfileExists(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("NSX Edge High Availability Profile resource %s not found in resources", resourceName) + } + + resourceID := rs.Primary.ID + if resourceID == "" { + return fmt.Errorf("NSX Edge High Availability Profile resource ID not set in resources ") + } + + client := nsx.NewClusterProfilesClient(connector) + structValue, err := client.Get(resourceID) + if err != nil { + return fmt.Errorf("error while retrieving Edge High Availability Profile ID %s. Error: %v", resourceID, err) + } + converter := bindings.NewTypeConverter() + o, errs := converter.ConvertToGolang(structValue, model.EdgeHighAvailabilityProfileBindingType()) + if errs != nil { + return errs[0] + } + obj := o.(model.EdgeHighAvailabilityProfile) + + if displayName == *obj.DisplayName { + return nil + } + return fmt.Errorf("NSX Edge High Availability Profile %s wasn't found", displayName) + } +} + +func testAccNSXEdgeHighAvailabilityProfileCheckDestroy(state *terraform.State, displayName string) error { + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + + // This addresses the fact that object is retrieved even though it had been deleted + time.Sleep(1 * time.Second) + + for _, rs := range state.RootModule().Resources { + if rs.Type != "nsxt_edge_high_availability_profile" { + continue + } + + resourceID := rs.Primary.Attributes["id"] + client := nsx.NewClusterProfilesClient(connector) + structValue, err := client.Get(resourceID) + + if isNotFoundError(err) { + return nil + } + + if err != nil { + return fmt.Errorf("error while retrieving Edge High Availability Profile ID %s. Error: %v", resourceID, err) + } + converter := bindings.NewTypeConverter() + o, errs := converter.ConvertToGolang(structValue, model.EdgeHighAvailabilityProfileBindingType()) + if errs != nil { + return errs[0] + } + obj := o.(model.EdgeHighAvailabilityProfile) + + if obj.DisplayName != nil && displayName == *obj.DisplayName { + return fmt.Errorf("NSX Edge High Availability Profile %s still exists", displayName) + } + } + + return nil +} + +func testAccNSXEdgeHighAvailabilityProfileCreateTemplate(displayName string) string { + return fmt.Sprintf(` +resource "nsxt_edge_high_availability_profile" "test" { + description = "Terraform test edge high availability profile" + display_name = "%s" + tag { + scope = "scope1" + tag = "tag1" + } +} +`, displayName) +} diff --git a/website/docs/r/edge_high_availability_profile.html.markdown b/website/docs/r/edge_high_availability_profile.html.markdown new file mode 100644 index 000000000..22867594a --- /dev/null +++ b/website/docs/r/edge_high_availability_profile.html.markdown @@ -0,0 +1,57 @@ +--- +subcategory: "Beta" +layout: "nsxt" +page_title: "NSXT: nsxt_edge_high_availability_profile" +description: A resource to configure an Edge High Availability Profile. +--- + +# nsxt_edge_high_availability_profile + +This resource provides a method for the management of an Edge High Availability Profile. +This resource is supported with NSX 4.1.0 onwards. + +## Example Usage + +```hcl +resource "nsxt_edge_high_availability_profile" "test" { + description = "Terraform provisioned Edge High Availability Profile" + display_name = "test" + 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. +* `tag` - (Optional) A list of scope + tag pairs to associate with this resource. +* `bfd_allowed_hops` - (Optional) BFD allowed hops. +* `bfd_declare_dead_multiple` - (Optional) Number of times a packet is missed before BFD declares the neighbor down. +* `bfd_probe_interval` - (Optional) the time interval (in millisecond) between probe packets for heartbeat purpose. +* `standby_relocation_threshold` - (Optional) Standby service context relocation wait time. + +## 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 Edge High Availability Profile can be [imported][docs-import] into this resource, via the following command: + +[docs-import]: https://www.terraform.io/cli/import + +``` +terraform import nsxt_edge_high_availability_profile.test UUID +``` + +The above command imports Edge High Availability Profile named `test` with the NSX Edge High Availability Profile +ID `UUID`.