Skip to content

Commit

Permalink
Add Resource NSX-T Route Advertisement (#858)
Browse files Browse the repository at this point in the history
Add resource `vcd_nsxt_route_advertisement` that allows NSX-T Edge Gateway to advertise subnets to Tier-0 Gateway  
Add datasource `vcd_nsxt_route_advertisement` that reads the NSX-T Edge Gateway routes that are being advertised
  • Loading branch information
Miguel Sama authored Jun 16, 2022
1 parent aa1853b commit ec2a510
Show file tree
Hide file tree
Showing 11 changed files with 814 additions and 10 deletions.
2 changes: 2 additions & 0 deletions .changes/v3.7.0/858-features.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
* Add resource `vcd_nsxt_route_advertisement` that allows NSX-T Edge Gateway to advertise subnets to Tier-0 Gateway [GH-858]
* Add datasource `vcd_nsxt_route_advertisement` that reads the NSX-T Edge Gateway routes that are being advertised [GH-858]
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ require (
github.com/hashicorp/go-version v1.5.0
github.com/hashicorp/terraform-plugin-sdk/v2 v2.17.0
github.com/kr/pretty v0.2.1
github.com/vmware/go-vcloud-director/v2 v2.16.0-alpha.7
github.com/vmware/go-vcloud-director/v2 v2.16.0-alpha.9
)
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,8 @@ github.com/vmihailenco/msgpack/v4 v4.3.12 h1:07s4sz9IReOgdikxLTKNbBdqDMLsjPKXwvC
github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4=
github.com/vmihailenco/tagparser v0.1.1 h1:quXMXlA39OCbd2wAdTsGDlK9RkOk6Wuw+x37wVyIuWY=
github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
github.com/vmware/go-vcloud-director/v2 v2.16.0-alpha.7 h1:sLzaQfSbcYq5Sm4VrrDZ+mMxFdCBGL3Nij1gtrlRcRg=
github.com/vmware/go-vcloud-director/v2 v2.16.0-alpha.7/go.mod h1:2BS1yw61VN34WI0/nUYoInFvBc3Zcuf84d4ESiAAl68=
github.com/vmware/go-vcloud-director/v2 v2.16.0-alpha.9 h1:i8OqjVqUoYuPcu/TByXxkxGg02xsX7X7lJlcjrl9y9o=
github.com/vmware/go-vcloud-director/v2 v2.16.0-alpha.9/go.mod h1:2BS1yw61VN34WI0/nUYoInFvBc3Zcuf84d4ESiAAl68=
github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI=
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
Expand Down
19 changes: 19 additions & 0 deletions vcd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,25 @@ func (cli *VCDClient) GetNsxtEdgeGatewayFromResourceById(d *schema.ResourceData,
return egw, nil
}

// GetOrgNameFromResource returns the Org name if set at resource level. If not, tries to get it from provider level.
// It errors if none is provided.
func (cli *VCDClient) GetOrgNameFromResource(d *schema.ResourceData) (string, error) {
orgName := d.Get("org").(string)
return cli.GetOrgName(orgName)
}

// GetOrgName returns the parameter orgName if provided. If not tried to get it from provider.
func (cli *VCDClient) GetOrgName(orgName string) (string, error) {
if orgName == "" {
orgName = cli.Org
}
if orgName == "" {
return "", fmt.Errorf("empty Org name provided")
}

return orgName, nil
}

func ProviderAuthenticate(client *govcd.VCDClient, user, password, token, org, apiToken string) error {
var err error
if apiToken != "" {
Expand Down
16 changes: 9 additions & 7 deletions vcd/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,15 @@ type TestConfig struct {
} `json:"peer"`
} `json:"networking"`
Nsxt struct {
Manager string `json:"manager"`
Tier0router string `json:"tier0router"`
Tier0routerVrf string `json:"tier0routervrf"`
Vdc string `json:"vdc"`
ExternalNetwork string `json:"externalNetwork"`
EdgeGateway string `json:"edgeGateway"`
NsxtImportSegment string `json:"nsxtImportSegment"`
Manager string `json:"manager"`
Tier0router string `json:"tier0router"`
Tier0routerVrf string `json:"tier0routervrf"`
Vdc string `json:"vdc"`
ExternalNetwork string `json:"externalNetwork"`
EdgeGateway string `json:"edgeGateway"`
VdcGroup string `json:"vdcGroup"`
VdcGroupEdgeGateway string `json:"vdcGroupEdgeGateway"`
NsxtImportSegment string `json:"nsxtImportSegment"`

NsxtAlbControllerUrl string `json:"nsxtAlbControllerUrl"`
NsxtAlbControllerUser string `json:"nsxtAlbControllerUser"`
Expand Down
73 changes: 73 additions & 0 deletions vcd/datasource_vcd_nsxt_route_advertisement.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package vcd

import (
"context"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func datasourceVcdNsxtRouteAdvertisement() *schema.Resource {
return &schema.Resource{
ReadContext: datasourceVcdNsxtRouteAdvertisementRead,

Schema: map[string]*schema.Schema{
"org": {
Type: schema.TypeString,
Optional: true,
Description: "The name of organization to use, optional if defined at provider " +
"level. Useful when connected as sysadmin working across different organizations",
},
"edge_gateway_id": {
Type: schema.TypeString,
Required: true,
Description: "NSX-T Edge Gateway ID in which route advertisement is located",
},
"enabled": {
Type: schema.TypeBool,
Computed: true,
Description: "Defines if route advertisement is active",
},
"subnets": {
Type: schema.TypeSet,
Computed: true,
Description: "Set of subnets that will be advertised to Tier-0 gateway. Empty means none",
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
},
}
}

func datasourceVcdNsxtRouteAdvertisementRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
vcdClient := meta.(*VCDClient)

edgeGatewayID := d.Get("edge_gateway_id").(string)

orgName, err := vcdClient.GetOrgNameFromResource(d)
if err != nil {
return diag.Errorf("error when getting Org name - %s", err)
}

nsxtEdge, err := vcdClient.GetNsxtEdgeGatewayById(orgName, edgeGatewayID)
if err != nil {
return diag.Errorf("error retrieving NSX-T Edge Gateway: %s", err)
}

routeAdvertisement, err := nsxtEdge.GetNsxtRouteAdvertisement()
if err != nil {
return diag.Errorf("error while retrieving route advertisement - %s", err)
}

dSet(d, "enabled", routeAdvertisement.Enable)

subnetSet := convertStringsToTypeSet(routeAdvertisement.Subnets)
err = d.Set("subnets", subnetSet)
if err != nil {
return diag.Errorf("error while setting subnets argument: %s", err)
}

d.SetId(edgeGatewayID)

return nil
}
2 changes: 2 additions & 0 deletions vcd/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ var globalDataSourceMap = map[string]*schema.Resource{
"vcd_vdc_group": datasourceVdcGroup(), // 3.5
"vcd_nsxt_distributed_firewall": datasourceVcdNsxtDistributedFirewall(), // 3.6
"vcd_nsxt_network_context_profile": datasourceVcdNsxtNetworkContextProfile(), // 3.6
"vcd_nsxt_route_advertisement": datasourceVcdNsxtRouteAdvertisement(), // 3.7

}

Expand Down Expand Up @@ -163,6 +164,7 @@ var globalResourceMap = map[string]*schema.Resource{
"vcd_vdc_group": resourceVdcGroup(), // 3.5
"vcd_nsxt_distributed_firewall": resourceVcdNsxtDistributedFirewall(), // 3.6
"vcd_security_tag": resourceVcdSecurityTag(), // 3.7
"vcd_nsxt_route_advertisement": resourceVcdNsxtRouteAdvertisement(), // 3.7
"vcd_org_vdc_access_control": resourceVcdOrgVdcAccessControl(), // 3.7
}

Expand Down
216 changes: 216 additions & 0 deletions vcd/resource_vcd_nsxt_route_advertisement.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
package vcd

import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/vmware/go-vcloud-director/v2/govcd"
"log"
"strings"
)

func resourceVcdNsxtRouteAdvertisement() *schema.Resource {
return &schema.Resource{
CreateContext: resourceVcdNsxtRouteAdvertisementCreateUpdate,
ReadContext: resourceVcdNsxtRouteAdvertisementRead,
UpdateContext: resourceVcdNsxtRouteAdvertisementCreateUpdate,
DeleteContext: resourceVcdNsxtRouteAdvertisementDelete,
Importer: &schema.ResourceImporter{
StateContext: resourceVcdNsxtRouteAdvertisementImport,
},
Schema: map[string]*schema.Schema{
"org": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Description: "The name of organization to use, optional if defined at provider " +
"level. Useful when connected as sysadmin working across different organizations",
},
"edge_gateway_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "NSX-T Edge Gateway ID in which route advertisement is located",
},
"enabled": {
Type: schema.TypeBool,
Optional: true,
Default: true,
Description: "Defines if route advertisement is active",
},
"subnets": {
Type: schema.TypeSet,
Optional: true,
Description: "Set of subnets that will be advertised to Tier-0 gateway. Empty means none",
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
},
}
}

func resourceVcdNsxtRouteAdvertisementCreateUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
vcdClient := meta.(*VCDClient)

// Handling locks on a route advertisement is conditional. There are two scenarios:
// * When the parent Edge Gateway is in a VDC - a lock on parent Edge Gateway must be acquired
// * When the parent Edge Gateway is in a VDC Group - a lock on parent VDC Group must be acquired
// To find out parent lock object, Edge Gateway must be looked up and its OwnerRef must be checked
// Note. It is not safe to do multiple locks in the same resource as it can result in a deadlock
parentEdgeGatewayOwnerId, _, err := getParentEdgeGatewayOwnerId(vcdClient, d)
if err != nil {
return diag.Errorf("[routed advertisement create/update] error finding parent Edge Gateway: %s", err)
}

if govcd.OwnerIsVdcGroup(parentEdgeGatewayOwnerId) {
vcdClient.lockById(parentEdgeGatewayOwnerId)
defer vcdClient.unlockById(parentEdgeGatewayOwnerId)
} else {
vcdClient.lockParentEdgeGtw(d)
defer vcdClient.unLockParentEdgeGtw(d)
}

var subnets []string
enableRouteAdvertisement := d.Get("enabled").(bool)
subnetsFromSchema, ok := d.GetOk("subnets")

if ok {
subnets = convertSchemaSetToSliceOfStrings(subnetsFromSchema.(*schema.Set))
}

if !enableRouteAdvertisement && len(subnets) > 0 {
return diag.Errorf("if enable is set to false, no subnets must be passed")
}

_, edgeGateway, err := getParentEdgeGatewayOwnerIdAndNsxtEdgeGateway(vcdClient, d, "route advertisement")
if err != nil {
return diag.FromErr(err)
}

err = checkNSXTEdgeGatewayDedicated(edgeGateway)
if err != nil {
return diag.Errorf("error when configuring route advertisement on NSX-T Edge Gateway - %s", err)
}

_, err = edgeGateway.UpdateNsxtRouteAdvertisement(enableRouteAdvertisement, subnets)
if err != nil {
return diag.Errorf("error when creating/updating route advertisement - %s", err)
}

d.SetId(edgeGateway.EdgeGateway.ID)

return resourceVcdNsxtRouteAdvertisementRead(ctx, d, meta)
}

func resourceVcdNsxtRouteAdvertisementRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
vcdClient := meta.(*VCDClient)

orgName, err := vcdClient.GetOrgNameFromResource(d)
if err != nil {
return diag.Errorf("error when getting Org name - %s", err)
}

nsxtEdge, err := vcdClient.GetNsxtEdgeGatewayById(orgName, d.Id())
if err != nil {
if govcd.ContainsNotFound(err) {
d.SetId("")
return nil
}
return diag.Errorf("error retrieving NSX-T Edge Gateway: %s", err)
}

routeAdvertisement, err := nsxtEdge.GetNsxtRouteAdvertisement()
if err != nil {
return diag.Errorf("error while retrieving route advertisement - %s", err)
}

dSet(d, "enabled", routeAdvertisement.Enable)

subnetSet := convertStringsToTypeSet(routeAdvertisement.Subnets)
err = d.Set("subnets", subnetSet)
if err != nil {
return diag.Errorf("error while setting subnets argument: %s", err)
}

return nil
}

func resourceVcdNsxtRouteAdvertisementDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
vcdClient := meta.(*VCDClient)

// Handling locks on a route advertisement is conditional. There are two scenarios:
// * When the parent Edge Gateway is in a VDC - a lock on parent Edge Gateway must be acquired
// * When the parent Edge Gateway is in a VDC Group - a lock on parent VDC Group must be acquired
// To find out parent lock object, Edge Gateway must be looked up and its OwnerRef must be checked
// Note. It is not safe to do multiple locks in the same resource as it can result in a deadlock
parentEdgeGatewayOwnerId, _, err := getParentEdgeGatewayOwnerId(vcdClient, d)
if err != nil {
return diag.Errorf("[route advertisement delete] error finding parent Edge Gateway: %s", err)
}

if govcd.OwnerIsVdcGroup(parentEdgeGatewayOwnerId) {
vcdClient.lockById(parentEdgeGatewayOwnerId)
defer vcdClient.unlockById(parentEdgeGatewayOwnerId)
} else {
vcdClient.lockParentEdgeGtw(d)
defer vcdClient.unLockParentEdgeGtw(d)
}

orgName, err := vcdClient.GetOrgNameFromResource(d)
if err != nil {
return diag.Errorf("error when getting Org name - %s", err)
}

nsxtEdge, err := vcdClient.GetNsxtEdgeGatewayById(orgName, d.Id())
if err != nil {
return diag.Errorf("error retrieving NSX-T Edge Gateway: %s", err)
}

err = nsxtEdge.DeleteNsxtRouteAdvertisement()
if err != nil {
return diag.Errorf("error while deleting route advertisement - %s", err)
}

d.SetId("")
return nil
}

func resourceVcdNsxtRouteAdvertisementImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
log.Printf("[TRACE] NSX-T Edge Gateway Route Advertisement import initiated")

resourceURI := strings.Split(d.Id(), ImportSeparator)
if len(resourceURI) != 3 {
return nil, fmt.Errorf("resource name must be specified as org-name.vdc-or-vdc-group-name.nsxt-edge-gw-name")
}
orgName, vdcOrVdcGroupName, edgeName := resourceURI[0], resourceURI[1], resourceURI[2]

vcdClient := meta.(*VCDClient)
vdcOrVdcGroup, err := lookupVdcOrVdcGroup(vcdClient, orgName, vdcOrVdcGroupName)
if err != nil {
return nil, err
}

edge, err := vdcOrVdcGroup.GetNsxtEdgeGatewayByName(edgeName)
if err != nil {
return nil, fmt.Errorf("could not retrieve NSX-T edge gateway with ID '%s': %s", d.Id(), err)
}

dSet(d, "org", orgName)

dSet(d, "edge_gateway_id", edge.EdgeGateway.ID)
d.SetId(edge.EdgeGateway.ID)

return []*schema.ResourceData{d}, nil
}

// checkNSXTEdgeGatewayDedicated is a simple helper function that checks if "Using Dedicated Provider Router" option is enabled
// on NSX-T Edge Gateway so that route advertisement can be configured. If not it returns an error.
func checkNSXTEdgeGatewayDedicated(nsxtEdgeGw *govcd.NsxtEdgeGateway) error {
if !nsxtEdgeGw.EdgeGateway.EdgeGatewayUplinks[0].Dedicated {
return fmt.Errorf("NSX-T Edge Gateway is not using a dedicated provider router. Please enable this feature before configuring route advertisement")
}

return nil
}
Loading

0 comments on commit ec2a510

Please sign in to comment.