diff --git a/nsxt/provider.go b/nsxt/provider.go index 597b464dd..b97318655 100644 --- a/nsxt/provider.go +++ b/nsxt/provider.go @@ -490,6 +490,7 @@ func Provider() *schema.Provider { "nsxt_policy_distributed_flood_protection_profile_binding": resourceNsxtPolicyDistributedFloodProtectionProfileBinding(), "nsxt_policy_gateway_flood_protection_profile": resourceNsxtPolicyGatewayFloodProtectionProfile(), "nsxt_policy_gateway_flood_protection_profile_binding": resourceNsxtPolicyGatewayFloodProtectionProfileBinding(), + "nsxt_policy_compute_sub_cluster": resourceNsxtPolicyComputeSubCluster(), "nsxt_policy_tier0_inter_vrf_routing": resourceNsxtPolicyTier0InterVRFRouting(), }, diff --git a/nsxt/resource_nsxt_policy_compute_sub_cluster.go b/nsxt/resource_nsxt_policy_compute_sub_cluster.go new file mode 100644 index 000000000..d77f6000a --- /dev/null +++ b/nsxt/resource_nsxt_policy_compute_sub_cluster.go @@ -0,0 +1,256 @@ +/* Copyright © 2024 Broadcom, 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/vmware/vsphere-automation-sdk-go/runtime/protocol/client" + + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/infra" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/infra/sites/enforcement_points" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" +) + +func resourceNsxtPolicyComputeSubCluster() *schema.Resource { + return &schema.Resource{ + Create: resourceNsxtPolicyComputeSubClusterCreate, + Read: resourceNsxtPolicyComputeSubClusterRead, + Update: resourceNsxtPolicyComputeSubClusterUpdate, + Delete: resourceNsxtPolicyComputeSubClusterDelete, + Importer: &schema.ResourceImporter{ + State: resourceNsxtPolicyComputeSubClusterImporter, + }, + + Schema: map[string]*schema.Schema{ + "nsx_id": getNsxIDSchema(), + "path": getPathSchema(), + "display_name": getDisplayNameSchema(), + "description": getDescriptionSchema(), + "revision": getRevisionSchema(), + "tag": getTagsSchema(), + "site_path": { + Type: schema.TypeString, + Description: "Path to the site this subcluster belongs to", + Optional: true, + ForceNew: true, + Default: defaultInfraSitePath, + ValidateFunc: validatePolicyPath(), + }, + "enforcement_point": { + Type: schema.TypeString, + Description: "ID of the enforcement point this subcluster belongs to", + Optional: true, + ForceNew: true, + Default: "default", + }, + "compute_collection_id": { + Type: schema.TypeString, + Description: "Compute collection ID under which subcluster is created", + Required: true, + }, + "discovered_node_ids": { + Type: schema.TypeList, + Description: "Discovered node IDs under this subcluster", + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + } +} + +func resourceNsxtPolicyComputeSubClusterRead(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + scClient := enforcement_points.NewSubClustersClient(connector) + + id, siteID, epID, err := policyIDSiteEPTuple(d, m) + if err != nil { + return err + } + + obj, err := scClient.Get(siteID, epID, id) + if err != nil { + return handleReadError(d, "SubCluster", id, err) + } + + d.Set("display_name", obj.DisplayName) + d.Set("description", obj.Description) + setPolicyTagsInSchema(d, obj.Tags) + d.Set("nsx_id", id) + d.Set("path", obj.Path) + d.Set("revision", obj.Revision) + + d.Set("compute_collection_id", obj.ComputeCollectionId) + if obj.SubClusterInfo != nil { + d.Set("discovered_node_ids", obj.SubClusterInfo.DiscoveredNodeIds) + } + + return nil +} + +func resourceNsxtPolicyComputeSubClusterExists(siteID, epID, id string, connector client.Connector) (bool, error) { + // Check site existence first + siteClient := infra.NewSitesClient(connector) + _, err := siteClient.Get(siteID) + if err != nil { + msg := fmt.Sprintf("failed to read site %s", siteID) + return false, logAPIError(msg, err) + } + scClient := enforcement_points.NewSubClustersClient(connector) + _, err = scClient.Get(siteID, epID, id) + + if err == nil { + return true, nil + } + + if isNotFoundError(err) { + return false, nil + } + + return false, logAPIError("Error retrieving service", err) +} + +func policyComputeSubClusterPatch(siteID, epID, id string, d *schema.ResourceData, m interface{}, isUpdate bool, isDelete bool) error { + connector := getPolicyConnector(m) + scClient := enforcement_points.NewSubClustersClient(connector) + + description := d.Get("description").(string) + displayName := d.Get("display_name").(string) + collectionID := d.Get("compute_collection_id").(string) + tags := getPolicyTagsFromSchema(d) + discoveredNodes := getStringListFromSchemaList(d, "discovered_node_ids") + revision := int64(d.Get("revision").(int)) + // Only manual type is supported for now + manual := model.SubClusterInfo_SUB_CLUSTER_TYPE_MANUAL + subClusterInfo := &model.SubClusterInfo{ + SubClusterType: &manual, + } + obj := model.SubCluster{ + Description: &description, + DisplayName: &displayName, + Tags: tags, + ComputeCollectionId: &collectionID, + SubClusterInfo: subClusterInfo, + } + + if len(discoveredNodes) > 0 && !isDelete { + // NSX requires us to remove all nodes before deletion, + // This Patch call will remove all nodes during delete flow, + // hence the line below is skipped + obj.SubClusterInfo.DiscoveredNodeIds = discoveredNodes + } + + if isUpdate { + obj.Revision = &revision + _, err := scClient.Update(siteID, epID, id, obj) + return err + } + + _, err := scClient.Patch(siteID, epID, id, obj) + return err +} + +func resourceNsxtPolicyComputeSubClusterCreate(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + id := d.Get("nsx_id").(string) + if id == "" { + id = newUUID() + } + sitePath := d.Get("site_path").(string) + siteID := getResourceIDFromResourcePath(sitePath, "sites") + if siteID == "" { + return fmt.Errorf("error obtaining Site ID from site path %s", sitePath) + } + epID := d.Get("enforcement_point").(string) + if epID == "" { + epID = getPolicyEnforcementPoint(m) + } + exists, err := resourceNsxtPolicyComputeSubClusterExists(siteID, epID, id, connector) + if err != nil { + return err + } + if exists { + return fmt.Errorf("resource with ID %s already exists", id) + } + + // Create the resource using PATCH + log.Printf("[INFO] Creating SubCluster with ID %s under site %s enforcement point %s", id, siteID, epID) + err = policyComputeSubClusterPatch(siteID, epID, id, d, m, false, false) + if err != nil { + return handleCreateError("SubCluster", id, err) + } + + d.SetId(id) + d.Set("nsx_id", id) + + return resourceNsxtPolicyComputeSubClusterRead(d, m) +} + +func resourceNsxtPolicyComputeSubClusterUpdate(d *schema.ResourceData, m interface{}) error { + id, siteID, epID, err := policyIDSiteEPTuple(d, m) + if err != nil { + return err + } + + log.Printf("[INFO] Updating SubCluster with ID %s", id) + err = policyComputeSubClusterPatch(siteID, epID, id, d, m, true, false) + if err != nil { + return handleUpdateError("SubCluster", id, err) + } + + return resourceNsxtPolicyComputeSubClusterRead(d, m) +} + +func resourceNsxtPolicyComputeSubClusterDelete(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + htnClient := enforcement_points.NewSubClustersClient(connector) + + id, siteID, epID, err := policyIDSiteEPTuple(d, m) + if err != nil { + return err + } + + // If nodes are present, NSX requires to delete them first + discoveredNodes := getStringListFromSchemaList(d, "discovered_node_ids") + if len(discoveredNodes) > 0 { + err = policyComputeSubClusterPatch(siteID, epID, id, d, m, true, true) + if err != nil { + return handleUpdateError("SubCluster", id, err) + } + } + + log.Printf("[INFO] Deleting SubCluster with ID %s", id) + err = htnClient.Delete(siteID, epID, id) + if err != nil { + return handleDeleteError("SubCluster", id, err) + } + + return nil +} + +func resourceNsxtPolicyComputeSubClusterImporter(d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { + importID := d.Id() + rd, err := nsxtPolicyPathResourceImporterHelper(d, m) + if err != nil { + return rd, err + } + + epID, err := getParameterFromPolicyPath("/enforcement-points/", "/sub-clusters/", importID) + if err != nil { + return nil, err + } + d.Set("enforcement_point", epID) + sitePath, err := getSitePathFromChildResourcePath(importID) + if err != nil { + return rd, err + } + d.Set("site_path", sitePath) + + return rd, nil +} diff --git a/website/docs/r/policy_compute_sub_cluster.html.markdown b/website/docs/r/policy_compute_sub_cluster.html.markdown new file mode 100644 index 000000000..16584ad60 --- /dev/null +++ b/website/docs/r/policy_compute_sub_cluster.html.markdown @@ -0,0 +1,52 @@ +--- +subcategory: "Beta" +layout: "nsxt" +page_title: "NSXT: nsxt_policy_compute_sub_cluster" +description: A resource to configure a Sub Cluster. +--- + +# nsxt_policy_compute_sub_cluster + +This resource provides a method for the management of Compute Sub Cluster. +This resource is supported with NSX 3.2.2 onwards. + +## Example Usage + +```hcl +resource "nsxt_policy_compute_sub_cluster" "test" { + display_name = "subcluster1" + compute_collection_id = data.nsxt_compute_collection.cc1.id + discovered_node_ids = data.nsxt_discover_node.dn.id +} +``` + +## 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. +* `site_path` - (Optional) The path of the site which this Sub Cluster belongs to. `path` field of the existing `nsxt_policy_site` can be used here. Defaults to default site path. +* `enforcement_point` - (Optional) The ID of enforcement point under given `site_path` to manage the Host Transport Node. Defaults to default enforcement point. +* `compute_collection_id` - (Required) ID of compute collection under which sub-cluster is created +* `discovered_node_ids` - (Optional) Discovered node IDs under this subcluster + +## 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. +* `discovered_node_ids` - (Optional) Discovered node IDs under this subcluster + +## Importing + +An existing Sub Cluster can be [imported][docs-import] into this resource, via the following command: + +[docs-import]: https://www.terraform.io/cli/import + +``` +terraform import nsxt_policy_compute_sub_cluster.test POLICY_PATH +``` +The above command imports Sub Cluster named `test` with the policy path `POLICY_PATH`.