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

Add support for Edge transport nodes which were created externally #1488

Merged
merged 1 commit into from
Dec 19, 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
136 changes: 98 additions & 38 deletions nsxt/resource_nsxt_edge_transport_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@ func resourceNsxtEdgeTransportNode() *schema.Resource {
"description": getDescriptionSchema(),
"display_name": getDisplayNameSchema(),
"tag": getTagsSchema(),
"node_id": {
Type: schema.TypeString,
Optional: true,
Computed: true,
Description: "Unique Id of the fabric node",
ConflictsWith: []string{"ip_addresses", "fqdn", "deployment_config", "external_id"},
},
"failure_domain": {
Type: schema.TypeString,
Optional: true,
Expand Down Expand Up @@ -671,43 +678,56 @@ func getTransportNodeFromSchema(d *schema.ResourceData, m interface{}) (*mpmodel
description := d.Get("description").(string)
displayName := d.Get("display_name").(string)
tags := getMPTagsFromSchema(d)
nodeID := d.Get("node_id").(string)
failureDomain := d.Get("failure_domain").(string)
hostSwitchSpec, err := getHostSwitchSpecFromSchema(d, m, nodeTypeEdge)
if err != nil {
return nil, fmt.Errorf("failed to create Transport Node: %v", err)
}

converter := bindings.NewTypeConverter()
var dataValue data.DataValue
var errs []error
var nodeDeploymentInfo *data.StructValue
if nodeID == "" {
/*
node_id attribute conflicts with node_deployment_info. As node_deployment_info is a complex object which has
attributes with default values, this schema property will always have values - therefore there is no simple way
to enforce this conflict within the provider (e.g check if node_id and node_deployment_info have values, then
fail.
So the provider will ignore node_deployment_info properties when node_id has a value - which would mean that
this edge appliance was created externally.
*/
log.Printf("node_id not specified, will deploy edge using values in deploymentConfig")
converter := bindings.NewTypeConverter()
var dataValue data.DataValue
var errs []error

externalID := d.Get("external_id").(string)
fqdn := d.Get("fqdn").(string)
ipAddresses := interfaceListToStringList(d.Get("ip_addresses").([]interface{}))
externalID := d.Get("external_id").(string)
fqdn := d.Get("fqdn").(string)
ipAddresses := interfaceListToStringList(d.Get("ip_addresses").([]interface{}))

deploymentConfig, err := getEdgeNodeDeploymentConfigFromSchema(d.Get("deployment_config"))
if err != nil {
return nil, err
}
nodeSettings, err := getEdgeNodeSettingsFromSchema(d.Get("node_settings"))
if err != nil {
return nil, err
}
node := mpmodel.EdgeNode{
ExternalId: &externalID,
Fqdn: &fqdn,
IpAddresses: ipAddresses,
DeploymentConfig: deploymentConfig,
NodeSettings: nodeSettings,
ResourceType: mpmodel.EdgeNode__TYPE_IDENTIFIER,
}
dataValue, errs = converter.ConvertToVapi(node, mpmodel.EdgeNodeBindingType())
deploymentConfig, err := getEdgeNodeDeploymentConfigFromSchema(d.Get("deployment_config"))
if err != nil {
return nil, err
}
nodeSettings, err := getEdgeNodeSettingsFromSchema(d.Get("node_settings"))
if err != nil {
return nil, err
}
node := mpmodel.EdgeNode{
ExternalId: &externalID,
Fqdn: &fqdn,
IpAddresses: ipAddresses,
DeploymentConfig: deploymentConfig,
NodeSettings: nodeSettings,
ResourceType: mpmodel.EdgeNode__TYPE_IDENTIFIER,
}
dataValue, errs = converter.ConvertToVapi(node, mpmodel.EdgeNodeBindingType())

if errs != nil {
log.Printf("Failed to convert node object, errors are %v", errs)
return nil, errs[0]
if errs != nil {
log.Printf("Failed to convert node object, errors are %v", errs)
return nil, errs[0]
}
nodeDeploymentInfo = dataValue.(*data.StructValue)
}
nodeDeploymentInfo := dataValue.(*data.StructValue)

obj := mpmodel.TransportNode{
Description: &description,
Expand All @@ -725,6 +745,37 @@ func resourceNsxtEdgeTransportNodeCreate(d *schema.ResourceData, m interface{})
connector := getPolicyConnector(m)
client := nsx.NewTransportNodesClient(connector)

nodeID := d.Get("node_id").(string)
if nodeID != "" {
obj, err := client.Get(nodeID)
if err != nil {
return handleCreateError("TransportNode", nodeID, err)
}
// Set node_id, revision and computed values in schema
d.Set("failure_domain", obj.FailureDomainId)

converter := bindings.NewTypeConverter()
base, errs := converter.ConvertToGolang(obj.NodeDeploymentInfo, mpmodel.EdgeNodeBindingType())
if errs != nil {
return handleCreateError("TransportNode", nodeID, errs[0])
}
node := base.(mpmodel.EdgeNode)
d.Set("external_id", node.ExternalId)
d.Set("fqdn", node.Fqdn)
d.Set("ip_addresses", node.IpAddresses)
if obj.Revision != nil {
d.Set("revision", obj.Revision)
}

d.SetId(nodeID)
err = resourceNsxtEdgeTransportNodeUpdate(d, m)
if err != nil {
// There is a failure in update, let's discard this so state will remain clean
d.SetId("")
}
return err
}

obj, err := getTransportNodeFromSchema(d, m)
if err != nil {
return err
Expand Down Expand Up @@ -914,23 +965,31 @@ func getCPUConfigFromSchema(cpuConfigList []interface{}) []mpmodel.CpuCoreConfig
func getHostSwitchProfileResourceType(m interface{}, id string) (string, error) {
connector := getPolicyConnector(m)
client := infra.NewHostSwitchProfilesClient(connector)
structValue, err := client.Get(id)

// we retrieve a list of profiles instead of using Get(), as the id could be either MP id or Policy id
list, err := client.List(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
if err != nil {
return "", err
}

converter := bindings.NewTypeConverter()
baseInterface, errs := converter.ConvertToGolang(structValue, model.PolicyBaseHostSwitchProfileBindingType())
if errs != nil {
return "", errs[0]
}
base := baseInterface.(model.PolicyBaseHostSwitchProfile)
resourceType, ok := mpHostSwitchProfileTypeFromPolicyType[base.ResourceType]
if !ok {
return "", fmt.Errorf("MP resource type not found for %s", base.ResourceType)

for _, structValue := range list.Results {
baseInterface, errs := converter.ConvertToGolang(structValue, model.PolicyBaseHostSwitchProfileBindingType())
if errs != nil {
return "", errs[0]
}
base := baseInterface.(model.PolicyBaseHostSwitchProfile)

if *base.Id == id || *base.RealizationId == id {
resourceType, ok := mpHostSwitchProfileTypeFromPolicyType[base.ResourceType]
if !ok {
return "", fmt.Errorf("MP resource type not found for %s", base.ResourceType)
}
return resourceType, nil
}
}
return resourceType, nil

return "", fmt.Errorf("Host Switch Profile type not found for %s", id)
}

func getHostSwitchProfileIDsFromSchema(m interface{}, hswProfileList []interface{}) ([]mpmodel.HostSwitchProfileTypeIdEntry, error) {
Expand Down Expand Up @@ -1236,6 +1295,7 @@ func resourceNsxtEdgeTransportNodeRead(d *schema.ResourceData, m interface{}) er
d.Set("description", obj.Description)
d.Set("display_name", obj.DisplayName)
setMPTagsInSchema(d, obj.Tags)
d.Set("node_id", obj.NodeId)
d.Set("failure_domain", obj.FailureDomainId)

if obj.HostSwitchSpec != nil {
Expand Down
2 changes: 1 addition & 1 deletion website/docs/d/transport_node_realization.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ resource "nsxt_transport_node" "test" {
}
}
node_settings {
hostname = "tf_edge_node"
hostname = "tf-edge-node"
allow_ssh_root_login = true
enable_ssh = true
}
Expand Down
35 changes: 34 additions & 1 deletion website/docs/r/edge_transport_node.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ resource "nsxt_edge_transport_node" "test" {
}
}
node_settings {
hostname = "tf_edge_node"
hostname = "tf-edge-node"
allow_ssh_root_login = true
enable_ssh = true
}
Expand All @@ -56,13 +56,46 @@ resource "nsxt_edge_transport_node" "test" {
**NOTE:** `data.vsphere_network`, `data.vsphere_compute_cluster`, `data.vsphere_datastore`, `data.vsphere_host` are
obtained using [hashicorp/vsphere](https://registry.terraform.io/providers/hashicorp/vsphere/) provider.

## Example Usage, with Edge Transport Node created externally and converted into a transport node using Terraform
```hcl
data "nsxt_transport_node" "test_node" {
display_name = "tf_edge_node"
}

resource "nsxt_edge_transport_node" "test_node" {
node_id = data.nsxt_transport_node.test_node.id
description = "Terraform-deployed edge node"
display_name = "tf_edge_node"
standard_host_switch {
ip_assignment {
static_ip_pool = data.nsxt_policy_ip_pool.vtep_ip_pool.realized_id
}
transport_zone_endpoint {
transport_zone = data.nsxt_transport_zone.overlay_tz.id
}
transport_zone_endpoint {
transport_zone = data.nsxt_transport_zone.vlan_tz.id
}
host_switch_profile = [data.nsxt_policy_uplink_host_switch_profile.edge_uplink_profile.path]
pnic {
device_name = "fp-eth0"
uplink_name = "uplink1"
}
}
node_settings {
hostname = "tf-edge-node"
}
}
```

## 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.
* `node_id` - (Optional) The id of a pre-deployed Edge appliance to be converted into a transport node. Note that `node_id` attribute conflicts with `external_id`, `fqdn`, `ip_addresses` `deployment_config` and `node_settings` and those will be ignored while specifying `node_id`.
* `failure_domain` - (Optional) Id of the failure domain.
* `standard_host_switch` - (Required) Standard host switch specification.
* `host_switch_id` - (Optional) The host switch id. This ID will be used to reference a host switch.
Expand Down
Loading