Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement policy site resource #1104

Merged
merged 1 commit into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions nsxt/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,7 @@ func Provider() *schema.Provider {
"nsxt_upgrade_prepare": resourceNsxtUpgradePrepare(),
"nsxt_upgrade_precheck_acknowledge": resourceNsxtUpgradePrecheckAcknowledge(),
"nsxt_policy_vtep_ha_host_switch_profile": resourceNsxtVtepHAHostSwitchProfile(),
"nsxt_policy_site": resourceNsxtPolicySite(),
},

ConfigureFunc: providerConfigure,
Expand Down
268 changes: 268 additions & 0 deletions nsxt/resource_nsxt_policy_site.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
/* Copyright © 2024 Broadcom, 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/protocol/client"
"github.com/vmware/vsphere-automation-sdk-go/services/nsxt-gm/global_infra"
gm_model "github.com/vmware/vsphere-automation-sdk-go/services/nsxt-gm/model"
)

var siteTypeValues = []string{
gm_model.Site_SITE_TYPE_ONPREM_LM,
gm_model.Site_SITE_TYPE_SDDC_LM,
}

func resourceNsxtPolicySite() *schema.Resource {
return &schema.Resource{
Create: resourceNsxtPolicySiteCreate,
Read: resourceNsxtPolicySiteRead,
Update: resourceNsxtPolicySiteUpdate,
Delete: resourceNsxtPolicySiteDelete,
Importer: &schema.ResourceImporter{
State: nsxtPolicyPathResourceImporter,
},

Schema: map[string]*schema.Schema{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you think site_number might be useful as a computed attribute?

Looking at the API spec, do you think we should include FederationConfig, remote_path, relative_path as computed too?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haven't found use for those yet, usually we add read-only attrs only if they're used as reference anywhere else or just seem very useful. As I haven't complete yet the federation object implementation, I might find their use later on maybe. Anyway it's always easier to add attributes than removing them.

"nsx_id": getNsxIDSchema(),
"path": getPathSchema(),
"display_name": getDisplayNameSchema(),
"description": getDescriptionSchema(),
"revision": getRevisionSchema(),
"tag": getTagsSchema(),
"fail_if_rtep_misconfigured": {
Type: schema.TypeBool,
Optional: true,
Description: "Fail onboarding if RTEPs misconfigured",
Default: true,
},
"fail_if_rtt_exceeded": {
Type: schema.TypeBool,
Optional: true,
Description: "Fail onboarding if maximum RTT exceeded",
Default: true,
},
"maximum_rtt": {
Type: schema.TypeInt,
Optional: true,
Description: "Maximum acceptable packet round trip time (RTT)",
Default: 250,
ValidateFunc: validation.IntBetween(0, 1000),
},
"site_connection_info": getConnectionInfoSchema(),
"site_type": {
Type: schema.TypeString,
Required: true,
Description: "Persistent Site Type",
ValidateFunc: validation.StringInSlice(siteTypeValues, false),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No default here? What happens if this is unspecified? Should the value be Computed in this case?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spec doesn't specify a default here.
As UI in GM allows only on-prem addition, maybe we could discard this attribute entirely?
Screenshot 2024-02-15 at 12 10 55

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this depends on env, and perhaps default depends on env as well. If NSX auto-assigns this value, we can define it as Computed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turns out this this is required...

│ Error:  Failed to create Site f5bb6846-cf9d-4efd-9cd6-4d11de593fff: Field level validation errors: {value  of property site_type is not one of the allowed values [ONPREM_LM, SDDC_LM]} (code 255)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe no need to set a default for now.
Let's try and understand better the use cases around SDDC_LM. If ONPREM_LM is used in 99% of cases, then we can set if as default in a follow-up PR.

},
},
}
}

func getConnectionInfoSchema() *schema.Schema {
return &schema.Schema{
Type: schema.TypeList,
Optional: true,
Description: "Connection information",
MaxItems: 3,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"fqdn": {
Type: schema.TypeString,
Optional: true,
Description: "Fully Qualified Domain Name of the Management Node",
},
"password": {
Type: schema.TypeString,
Optional: true,
Sensitive: true,
Description: "Password",
},
"site_uuid": {
Type: schema.TypeString,
Optional: true,
Description: "id of Site",
},
"thumbprint": {
Type: schema.TypeString,
Optional: true,
Description: "Thumbprint of Enforcement Point",
},
"username": {
Type: schema.TypeString,
Optional: true,
Description: "Username",
},
},
},
}
}

func getConnectionInfosFromSchema(d *schema.ResourceData, key string) []gm_model.SiteNodeConnectionInfo {
var connectionInfos []gm_model.SiteNodeConnectionInfo
connectionInfoList := d.Get(key).([]interface{})
for _, sci := range connectionInfoList {
elem := sci.(map[string]interface{})
fqdn := elem["fqdn"].(string)
password := elem["password"].(string)
siteUUID := elem["site_uuid"].(string)
thumbprint := elem["thumbprint"].(string)
username := elem["username"].(string)

connectionInfo := gm_model.SiteNodeConnectionInfo{
Fqdn: &fqdn,
Password: &password,
SiteUuid: &siteUUID,
Thumbprint: &thumbprint,
Username: &username,
}
connectionInfos = append(connectionInfos, connectionInfo)
}
return connectionInfos
}

func setConnectionInfosInSchema(d *schema.ResourceData, connectionInfo []gm_model.SiteNodeConnectionInfo, key string) {
var connectionInfos []interface{}
for _, sci := range connectionInfo {
elem := make(map[string]interface{})
elem["fqdn"] = sci.Fqdn
elem["password"] = sci.Password
elem["site_uuid"] = sci.SiteUuid
elem["thumbprint"] = sci.Thumbprint
elem["username"] = sci.Username

connectionInfos = append(connectionInfos, elem)
}
d.Set(key, connectionInfos)
}

func getSiteFromSchema(d *schema.ResourceData) gm_model.Site {
displayName := d.Get("display_name").(string)
description := d.Get("description").(string)
tags := getGMTagsFromSchema(d)
failIfRtepMisconfigured := d.Get("fail_if_rtep_misconfigured").(bool)
failIfRttExceeded := d.Get("fail_if_rtt_exceeded").(bool)
maximumRtt := int64(d.Get("maximum_rtt").(int))
siteConnectionInfos := getConnectionInfosFromSchema(d, "site_connection_info")
siteType := d.Get("site_type").(string)

return gm_model.Site{
DisplayName: &displayName,
Description: &description,
Tags: tags,
FailIfRtepMisconfigured: &failIfRtepMisconfigured,
FailIfRttExceeded: &failIfRttExceeded,
MaximumRtt: &maximumRtt,
SiteConnectionInfo: siteConnectionInfos,
SiteType: &siteType,
}
}

func resourceNsxtPolicySiteExists(id string, connector client.Connector, isGlobal bool) (bool, error) {
client := global_infra.NewSitesClient(connector)
_, err := client.Get(id)
if err == nil {
return true, nil
}

if isNotFoundError(err) {
return false, nil
}

return false, logAPIError("Error retrieving resource", err)
}

func resourceNsxtPolicySiteCreate(d *schema.ResourceData, m interface{}) error {
if !isPolicyGlobalManager(m) {
return globalManagerOnlyError()
}
id, err := getOrGenerateID(d, m, resourceNsxtPolicySiteExists)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add here:
if !isPolicyGlobalManager(m) {
return globalManagerOnlyError()
}

if err != nil {
return err
}

connector := getPolicyConnector(m)
client := global_infra.NewSitesClient(connector)
site := getSiteFromSchema(d)

err = client.Patch(id, site)
if err != nil {
return handleCreateError("Site", id, err)
}

d.SetId(id)
d.Set("nsx_id", id)

return resourceNsxtPolicySiteRead(d, m)
}

func resourceNsxtPolicySiteRead(d *schema.ResourceData, m interface{}) error {
connector := getPolicyConnector(m)

id := d.Id()
if id == "" {
return fmt.Errorf("error obtaining Site ID")
}
client := global_infra.NewSitesClient(connector)
obj, err := client.Get(id)
if err != nil {
return handleReadError(d, "Site", id, err)
}
d.Set("display_name", obj.DisplayName)
d.Set("description", obj.Description)
setGMTagsInSchema(d, obj.Tags)
d.Set("nsx_id", id)
d.Set("path", obj.Path)
d.Set("revision", obj.Revision)

d.Set("fail_if_rtep_misconfigured", obj.FailIfRtepMisconfigured)
d.Set("fail_if_rtt_exceeded", obj.FailIfRttExceeded)
d.Set("maximum_rtt", obj.MaximumRtt)

setConnectionInfosInSchema(d, obj.SiteConnectionInfo, "site_connection_info")
d.Set("site_type", obj.SiteType)

return nil
}

func resourceNsxtPolicySiteUpdate(d *schema.ResourceData, m interface{}) error {
id := d.Id()
if id == "" {
return fmt.Errorf("error obtaining Site ID")
}

connector := getPolicyConnector(m)
client := global_infra.NewSitesClient(connector)

obj := getSiteFromSchema(d)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not clear to me the behaviour that NSX will have if the update site_connection_info.
Did we test it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't try to set it (I need an extra LM to test this). But connectivity info is updatable in UI.

revision := int64(d.Get("revision").(int))
obj.Revision = &revision

_, err := client.Update(id, obj)
if err != nil {
return handleUpdateError("Site", id, err)
}

return nil
}

func resourceNsxtPolicySiteDelete(d *schema.ResourceData, m interface{}) error {
id := d.Id()
if id == "" {
return fmt.Errorf("error obtaining Site ID")
}
connector := getPolicyConnector(m)
client := global_infra.NewSitesClient(connector)
err := client.Delete(id, nil)
if err != nil {
return handleDeleteError("Site", id, err)
}

return nil
}
39 changes: 39 additions & 0 deletions nsxt/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/vmware/go-vmware-nsxt/common"
"github.com/vmware/go-vmware-nsxt/manager"
"github.com/vmware/vsphere-automation-sdk-go/runtime/protocol/client"
gm_model "github.com/vmware/vsphere-automation-sdk-go/services/nsxt-gm/model"
mp_model "github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp/nsx/model"
"github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp/nsx/node"
"github.com/vmware/vsphere-automation-sdk-go/services/nsxt/search"
Expand Down Expand Up @@ -780,3 +781,41 @@ func getMPTagsFromSchema(d *schema.ResourceData) []mp_model.Tag {
func setMPTagsInSchema(d *schema.ResourceData, tags []mp_model.Tag) {
setCustomizedMPTagsInSchema(d, tags, "tag")
}

func getCustomizedGMTagsFromSchema(d *schema.ResourceData, schemaName string) []gm_model.Tag {
tags := d.Get(schemaName).(*schema.Set).List()
tagList := make([]gm_model.Tag, 0)
for _, tag := range tags {
data := tag.(map[string]interface{})
scope := data["scope"].(string)
tag := data["tag"].(string)
elem := gm_model.Tag{
Scope: &scope,
Tag: &tag}

tagList = append(tagList, elem)
}
return tagList
}

func setCustomizedGMTagsInSchema(d *schema.ResourceData, tags []gm_model.Tag, schemaName string) {
var tagList []map[string]string
for _, tag := range tags {
elem := make(map[string]string)
elem["scope"] = *tag.Scope
elem["tag"] = *tag.Tag
tagList = append(tagList, elem)
}
err := d.Set(schemaName, tagList)
if err != nil {
log.Printf("[WARNING] Failed to set tag in schema: %v", err)
}
}

func getGMTagsFromSchema(d *schema.ResourceData) []gm_model.Tag {
return getCustomizedGMTagsFromSchema(d, "tag")
}

func setGMTagsInSchema(d *schema.ResourceData, tags []gm_model.Tag) {
setCustomizedGMTagsInSchema(d, tags, "tag")
}
65 changes: 65 additions & 0 deletions website/docs/r/policy_site.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
subcategory: "Beta"
layout: "nsxt"
page_title: "NSXT: nsxt_policy_site"
description: A resource to configure Policy Site.
---

# nsxt_policy_site

This resource provides a method for the management of Policy Site.

This resource is applicable to NSX Global Manager.

## Example Usage

```hcl
resource "nsxt_policy_site" "test" {
display_name = "test"
description = "Terraform provisioned Site"
site_connection_info {
fqdn = "192.168.230.230"
username = "admin"
password = "somepasswd"
thumbprint = "207d65dcb6f17aa5a1ef2365ee6ae0b396867baa92464e5f8a46f6853708b9ef"
}
site_type = "ONPREM_LM"
}
```
## 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.
* `nsx_id` - (Optional) The NSX ID of this resource. If set, this ID will be used to create the resource.
* `fail_if_rtep_misconfigured` - (Optional) Fail onboarding if RTEPs misconfigured. Default is true.
* `fail_if_rtt_exceeded` - (Optional) Fail onboarding if maximum RTT exceeded. Default is true.
* `maximum_rtt` - (Optional) Maximum acceptable packet round trip time (RTT). Default is 250.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The NSX API guide does not mention it, but I think we should specify the unit of measure.
msecs, I believe.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IDK which are the units as I couldn't find that attribute in the UI

* `site_connection_info` - (Optional) Connection information.
* `fqdn` - (Optional) Fully Qualified Domain Name of the Management Node.
* `password` - (Optional) Password.
* `site_uuid` - (Optional) ID of Site.
* `thumbprint` - (Optional) Thumbprint of Enforcement Point.
* `username` - (Optional) Username.
* `site_type` - (Required) Persistent Site Type. Allowed values are `ONPREM_LM`, `SDDC_LM`.

## 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.
* `path` - The NSX path of the policy resource.

## Importing

An existing object can be [imported][docs-import] into this resource, via the following command:

[docs-import]: https://www.terraform.io/cli/import

```
terraform import nsxt_policy_site.test POLICY_PATH
```
The above command imports Site named `test` with policy path `POLICY_PATH`.
Loading