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 NSX-T Edge Gateway BGP Neighbor configuration #489

Merged
merged 5 commits into from
Jul 27, 2022
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
3 changes: 3 additions & 0 deletions .changes/v2.16.0/489-features.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
* Add support for managing NSX-T Edge Gateway BGP Neighbor. It is done by adding types `EdgeBgpNeighbor` and
`types.EdgeBgpNeighbor` with functions `CreateBgpNeighbor`, `GetAllBgpNeighbors`,
`GetBgpNeighborByIp`, `GetBgpNeighborById`, `Update` and `Delete` [GH-489]
239 changes: 239 additions & 0 deletions govcd/nsxt_edgegateway_bgp_neighbor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
/*
* Copyright 2022 VMware, Inc. All rights reserved. Licensed under the Apache v2 License.
*/

package govcd

import (
"fmt"
"net/url"

"github.com/vmware/go-vcloud-director/v2/types/v56"
)

// EdgeBgpNeighbor represents NSX-T Edge Gateway BGP Neighbor
type EdgeBgpNeighbor struct {
EdgeBgpNeighbor *types.EdgeBgpNeighbor
client *Client
// edgeGatewayId is stored for usage in EdgeBgpNeighbor receiver functions
edgeGatewayId string
}

// CreateBgpNeighbor creates BGP Neighbor with the given configuration
func (egw *NsxtEdgeGateway) CreateBgpNeighbor(bgpNeighborConfig *types.EdgeBgpNeighbor) (*EdgeBgpNeighbor, error) {
client := egw.client
endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointEdgeBgpNeighbor
apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
if err != nil {
return nil, err
}

// Insert Edge Gateway ID into endpoint path
urlRef, err := client.OpenApiBuildEndpoint(fmt.Sprintf(endpoint, egw.EdgeGateway.ID))
if err != nil {
return nil, err
}

returnObject := &EdgeBgpNeighbor{
client: egw.client,
edgeGatewayId: egw.EdgeGateway.ID,
EdgeBgpNeighbor: &types.EdgeBgpNeighbor{},
}

task, err := client.OpenApiPostItemAsync(apiVersion, urlRef, nil, bgpNeighborConfig)
if err != nil {
return nil, fmt.Errorf("error creating NSX-T Edge Gateway BGP Neighbor: %s", err)
}

err = task.WaitTaskCompletion()
if err != nil {
return nil, fmt.Errorf("error creating NSX-T Edge Gateway BGP Neighbor: %s", err)
}

// API has problems therefore explicit manual handling is required to lookup newly created object
// VCD 10.2 -> no ID for newly created object is returned at all
// VCD 10.3 -> `Details` field in task contains ID of newly created object
// To cover all cases this code will at first look for ID in `Details` field and fall back to
// lookup by name if `Details` field is empty.
//
// The drawback of this is that it is possible to create duplicate records with the same name on VCDs that don't
// return IDs, but there is no better way for VCD versions that don't return API code

bgpNeighborId := task.Task.Details
if bgpNeighborId != "" {
getUrlRef, err := client.OpenApiBuildEndpoint(fmt.Sprintf(endpoint, egw.EdgeGateway.ID), bgpNeighborId)
if err != nil {
return nil, err
}
err = client.OpenApiGetItem(apiVersion, getUrlRef, nil, returnObject.EdgeBgpNeighbor, nil)
if err != nil {
return nil, fmt.Errorf("error retrieving NSX-T Edge Gateway BGP Neighbor after creation: %s", err)
}
} else {
// ID after object creation was not returned therefore retrieving the entity by Name to lookup ID
// This has a risk of duplicate items, but is the only way to find the object when ID is not returned
bgpNeighbor, err := egw.GetBgpNeighborByIp(bgpNeighborConfig.NeighborAddress)
if err != nil {
return nil, fmt.Errorf("error retrieving NSX-T Edge Gateway BGP Neighbor after creation: %s", err)
}
returnObject = bgpNeighbor
}

return returnObject, nil
}

// GetAllBgpNeighbors retrieves all BGP Neighbors with an optional filter
func (egw *NsxtEdgeGateway) GetAllBgpNeighbors(queryParameters url.Values) ([]*EdgeBgpNeighbor, error) {
queryParams := copyOrNewUrlValues(queryParameters)

client := egw.client
endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointEdgeBgpNeighbor
apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
if err != nil {
return nil, err
}

// Insert Edge Gateway ID into endpoint path
urlRef, err := client.OpenApiBuildEndpoint(fmt.Sprintf(endpoint, egw.EdgeGateway.ID))
if err != nil {
return nil, err
}

typeResponses := []*types.EdgeBgpNeighbor{{}}
err = client.OpenApiGetAllItems(apiVersion, urlRef, queryParams, &typeResponses, nil)
if err != nil {
return nil, err
}

wrappedResponses := make([]*EdgeBgpNeighbor, len(typeResponses))
for sliceIndex := range typeResponses {
wrappedResponses[sliceIndex] = &EdgeBgpNeighbor{
EdgeBgpNeighbor: typeResponses[sliceIndex],
client: client,
edgeGatewayId: egw.EdgeGateway.ID,
}
}

return wrappedResponses, nil
}

// GetBgpNeighborByIp retrieves BGP Neighbor by Neighbor IP address
// It is meant to retrieve exactly one entry:
// * Will fail if more than one entry with the same Neighbor IP found (should not happen as uniqueness is
// enforced by API)
// * Will return an error containing `ErrorEntityNotFound` if no entries are found
//
// Note. API does not support filtering by 'neighborIpAddress' field therefore filtering is performed on client
// side
func (egw *NsxtEdgeGateway) GetBgpNeighborByIp(neighborIpAddress string) (*EdgeBgpNeighbor, error) {
if neighborIpAddress == "" {
return nil, fmt.Errorf("neighborIpAddress cannot be empty")
}

allBgpNeighbors, err := egw.GetAllBgpNeighbors(nil)
if err != nil {
return nil, fmt.Errorf("error retrieving NSX-T Edge Gateway BGP Neighbor: %s", err)
}

var filteredBgpNeighbors []*EdgeBgpNeighbor
for _, bgpNeighbor := range allBgpNeighbors {
if bgpNeighbor.EdgeBgpNeighbor.NeighborAddress == neighborIpAddress {
filteredBgpNeighbors = append(filteredBgpNeighbors, bgpNeighbor)
}
}

if len(filteredBgpNeighbors) > 1 {
return nil, fmt.Errorf("more than one NSX-T Edge Gateway BGP Neighbor found with IP Address '%s'", neighborIpAddress)
}

if len(filteredBgpNeighbors) == 0 {
return nil, fmt.Errorf("%s: no NSX-T Edge Gateway BGP Neighbor found with IP Address '%s'", ErrorEntityNotFound, neighborIpAddress)
}

return filteredBgpNeighbors[0], nil
}

// GetBgpNeighborById retrieves BGP Neighbor By ID
func (egw *NsxtEdgeGateway) GetBgpNeighborById(id string) (*EdgeBgpNeighbor, error) {
if id == "" {
return nil, fmt.Errorf("id is required")
}

client := egw.client
endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointEdgeBgpNeighbor
apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
if err != nil {
return nil, err
}

// Insert Edge Gateway ID into endpoint path
urlRef, err := client.OpenApiBuildEndpoint(fmt.Sprintf(endpoint, egw.EdgeGateway.ID), id)
if err != nil {
return nil, err
}

returnObject := &EdgeBgpNeighbor{
client: egw.client,
edgeGatewayId: egw.EdgeGateway.ID,
EdgeBgpNeighbor: &types.EdgeBgpNeighbor{},
}

err = client.OpenApiGetItem(apiVersion, urlRef, nil, returnObject.EdgeBgpNeighbor, nil)
if err != nil {
return nil, fmt.Errorf("error retrieving NSX-T Edge Gateway BGP Neighbor: %s", err)
}

return returnObject, nil
}

// Update updates existing BGP Neighbor with new configuration and returns it
func (bgpNeighbor *EdgeBgpNeighbor) Update(bgpNeighborConfig *types.EdgeBgpNeighbor) (*EdgeBgpNeighbor, error) {
client := bgpNeighbor.client
endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointEdgeBgpNeighbor
apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
if err != nil {
return nil, err
}

// Insert Edge Gateway ID into endpoint path
urlRef, err := client.OpenApiBuildEndpoint(fmt.Sprintf(endpoint, bgpNeighbor.edgeGatewayId), bgpNeighborConfig.ID)
if err != nil {
return nil, err
}

returnObject := &EdgeBgpNeighbor{
client: bgpNeighbor.client,
edgeGatewayId: bgpNeighbor.edgeGatewayId,
EdgeBgpNeighbor: &types.EdgeBgpNeighbor{},
}

err = client.OpenApiPutItem(apiVersion, urlRef, nil, bgpNeighborConfig, returnObject.EdgeBgpNeighbor, nil)
if err != nil {
return nil, fmt.Errorf("error setting NSX-T Edge Gateway BGP Neighbor: %s", err)
}

return returnObject, nil
}

// Delete deletes existing BGP Neighbor
func (bgpNeighbor *EdgeBgpNeighbor) Delete() error {
client := bgpNeighbor.client
endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointEdgeBgpNeighbor
apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
if err != nil {
return err
}

// Insert Edge Gateway ID into endpoint path
urlRef, err := client.OpenApiBuildEndpoint(fmt.Sprintf(endpoint, bgpNeighbor.edgeGatewayId), bgpNeighbor.EdgeBgpNeighbor.ID)
if err != nil {
return err
}

err = client.OpenApiDeleteItem(apiVersion, urlRef, nil, nil)
if err != nil {
return fmt.Errorf("error deleting NSX-T Edge Gateway BGP Neighbor: %s", err)
}

return nil
}
83 changes: 83 additions & 0 deletions govcd/nsxt_edgegateway_bgp_neighbor_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//go:build network || nsxt || functional || openapi || ALL
// +build network nsxt functional openapi ALL

package govcd

import (
"github.com/vmware/go-vcloud-director/v2/types/v56"
. "gopkg.in/check.v1"
)

func (vcd *TestVCD) Test_NsxEdgeBgpNeighbor(check *C) {
skipNoNsxtConfiguration(vcd, check)
skipOpenApiEndpointTest(vcd, check, types.OpenApiPathVersion1_0_0+types.OpenApiEndpointEdgeBgpNeighbor)

org, err := vcd.client.GetOrgByName(vcd.config.VCD.Org)
check.Assert(err, IsNil)
nsxtVdc, err := org.GetVDCByName(vcd.config.VCD.Nsxt.Vdc, false)
check.Assert(err, IsNil)
edge, err := nsxtVdc.GetNsxtEdgeGatewayByName(vcd.config.VCD.Nsxt.EdgeGateway)
check.Assert(err, IsNil)

// Switch Edge Gateway to use dedicated uplink for the time of this test and then turn it off
err = switchEdgeGatewayDedication(edge, true) // Turn on Dedicated Tier 0 gateway
check.Assert(err, IsNil)
defer switchEdgeGatewayDedication(edge, false) // Turn off Dedicated Tier 0 gateway

// Create a new BGP IP Neighbor
bgpNeighbor := &types.EdgeBgpNeighbor{
NeighborAddress: "11.11.11.11",
RemoteASNumber: "64123",
KeepAliveTimer: 80,
HoldDownTimer: 241,
NeighborPassword: "iQuee-ph2phe",
AllowASIn: true,
GracefulRestartMode: "HELPER_ONLY",
IpAddressTypeFiltering: "DISABLED",
}

createdBgpNeighbor, err := edge.CreateBgpNeighbor(bgpNeighbor)
check.Assert(err, IsNil)
check.Assert(createdBgpNeighbor, NotNil)

// Get all BGP Neighbors
BgpNeighbors, err := edge.GetAllBgpNeighbors(nil)
check.Assert(err, IsNil)
check.Assert(BgpNeighbors, NotNil)
check.Assert(len(BgpNeighbors), Equals, 1)
check.Assert(BgpNeighbors[0].EdgeBgpNeighbor.NeighborAddress, Equals, bgpNeighbor.NeighborAddress)

// Get BGP Neighbor By Neighbor IP Address
bgpNeighborByIp, err := edge.GetBgpNeighborByIp(bgpNeighbor.NeighborAddress)
check.Assert(err, IsNil)
check.Assert(bgpNeighborByIp, NotNil)

// Get BGP Neighbor By Id
bgpNeighborById, err := edge.GetBgpNeighborById(createdBgpNeighbor.EdgeBgpNeighbor.ID)
check.Assert(err, IsNil)
check.Assert(bgpNeighborById, NotNil)

// Update BGP Neighbor
bgpNeighbor.NeighborAddress = "12.12.12.12"
bgpNeighbor.ID = BgpNeighbors[0].EdgeBgpNeighbor.ID

updatedBgpNeighbor, err := BgpNeighbors[0].Update(bgpNeighbor)
check.Assert(err, IsNil)
check.Assert(updatedBgpNeighbor, NotNil)

check.Assert(updatedBgpNeighbor.EdgeBgpNeighbor.ID, Equals, BgpNeighbors[0].EdgeBgpNeighbor.ID)

// Delete BGP Neighbor
err = BgpNeighbors[0].Delete()
check.Assert(err, IsNil)

// Try to get deleted BGP Neighbor once again and ensure it is not there
notFoundByIp, err := edge.GetBgpNeighborByIp(bgpNeighbor.NeighborAddress)
check.Assert(err, NotNil)
check.Assert(notFoundByIp, IsNil)

notFoundById, err := edge.GetBgpNeighborById(createdBgpNeighbor.EdgeBgpNeighbor.ID)
check.Assert(err, NotNil)
check.Assert(notFoundById, IsNil)

}
6 changes: 4 additions & 2 deletions govcd/openapi_endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,10 @@ var endpointMinApiVersions = map[string]string{
types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointSSLCertificateLibraryOld: "35.0", // VCD 10.2+ and deprecated from 10.3
types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointVdcGroupsDfwRules: "35.0", // VCD 10.2+
types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointNetworkContextProfiles: "35.0", // VCD 10.2+
types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointEdgeBgpConfigPrefixLists: "35.0", // VCD 10.2+
types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointEdgeBgpConfig: "35.0", // VCD 10.2+

types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointEdgeBgpNeighbor: "35.0", // VCD 10.2+
types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointEdgeBgpConfigPrefixLists: "35.0", // VCD 10.2+
types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointEdgeBgpConfig: "35.0", // VCD 10.2+
}

// elevateNsxtNatRuleApiVersion helps to elevate API version to consume newer NSX-T NAT Rule features
Expand Down
1 change: 1 addition & 0 deletions types/v56/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@ const (
OpenApiEndpointSecurityTags = "securityTags"
OpenApiEndpointNsxtRouteAdvertisement = "edgeGateways/%s/routing/advertisement"

OpenApiEndpointEdgeBgpNeighbor = "edgeGateways/%s/routing/bgp/neighbors/" // '%s' is NSX-T Edge Gateway ID
OpenApiEndpointEdgeBgpConfigPrefixLists = "edgeGateways/%s/routing/bgp/prefixLists/" // '%s' is NSX-T Edge Gateway ID
OpenApiEndpointEdgeBgpConfig = "edgeGateways/%s/routing/bgp" // '%s' is NSX-T Edge Gateway ID

Expand Down
Loading