diff --git a/.changes/v2.16.0/489-features.md b/.changes/v2.16.0/489-features.md new file mode 100644 index 000000000..35c0d3de6 --- /dev/null +++ b/.changes/v2.16.0/489-features.md @@ -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] diff --git a/govcd/nsxt_edgegateway_bgp_neighbor.go b/govcd/nsxt_edgegateway_bgp_neighbor.go new file mode 100644 index 000000000..60bfc72ce --- /dev/null +++ b/govcd/nsxt_edgegateway_bgp_neighbor.go @@ -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 +} diff --git a/govcd/nsxt_edgegateway_bgp_neighbor_test.go b/govcd/nsxt_edgegateway_bgp_neighbor_test.go new file mode 100644 index 000000000..c7c22be47 --- /dev/null +++ b/govcd/nsxt_edgegateway_bgp_neighbor_test.go @@ -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) + +} diff --git a/govcd/openapi_endpoints.go b/govcd/openapi_endpoints.go index 079cfe127..9cd9fa8c2 100644 --- a/govcd/openapi_endpoints.go +++ b/govcd/openapi_endpoints.go @@ -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 diff --git a/types/v56/constants.go b/types/v56/constants.go index 2d111fdb7..476bc4e29 100644 --- a/types/v56/constants.go +++ b/types/v56/constants.go @@ -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 diff --git a/types/v56/nsxt_types.go b/types/v56/nsxt_types.go index 6cf89393a..3251922eb 100644 --- a/types/v56/nsxt_types.go +++ b/types/v56/nsxt_types.go @@ -1283,7 +1283,79 @@ type RouteAdvertisement struct { Subnets []string `json:"subnets"` } -// EdgeBgpIpPrefixList holds BGP IP Prefix List configuration for NSX-T Edge Gateways +// EdgeBgpNeighbor represents a BGP neighbor on the NSX-T Edge Gateway +type EdgeBgpNeighbor struct { + ID string `json:"id,omitempty"` + + // NeighborAddress holds IP address of the BGP neighbor. Both IPv4 and IPv6 formats are supported. + // + // Note. Uniqueness is enforced by NeighborAddress + NeighborAddress string `json:"neighborAddress"` + + // RemoteASNumber specified Autonomous System (AS) number of a BGP neighbor in ASPLAIN format. + RemoteASNumber string `json:"remoteASNumber"` + + // KeepAliveTimer specifies the time interval (in seconds) between keep alive messages sent to + // peer. + KeepAliveTimer int `json:"keepAliveTimer,omitempty"` + + // HoldDownTimer specifies the time interval (in seconds) before declaring a peer dead. + HoldDownTimer int `json:"holdDownTimer,omitempty"` + + // NeighborPassword for BGP neighbor authentication. Empty string ("") clears existing password. + // Not specifying a value will be treated as "no password". + NeighborPassword string `json:"neighborPassword"` + + // AllowASIn is a flag indicating whether BGP neighbors can receive routes with same AS. + AllowASIn bool `json:"allowASIn,omitempty"` + + // GracefulRestartMode Describes Graceful Restart configuration Modes for BGP configuration on + // an Edge Gateway. + // + // Possible values are: DISABLE , HELPER_ONLY , GRACEFUL_AND_HELPER + // * DISABLE - Both graceful restart and helper modes are disabled. + // * HELPER_ONLY - Only helper mode is enabled. (ability for a BGP speaker to indicate its ability to preserve + // forwarding state during BGP restart + // * GRACEFUL_AND_HELPER - Both graceful restart and helper modes are enabled. Ability of a BGP + // speaker to advertise its restart to its peers. + GracefulRestartMode string `json:"gracefulRestartMode,omitempty"` + + // IpAddressTypeFiltering specifies IP address type based filtering in each direction. Setting + // the value to "DISABLED" will disable address family based filtering. + // + // Possible values are: IPV4 , IPV6 , DISABLED + IpAddressTypeFiltering string `json:"ipAddressTypeFiltering,omitempty"` + + // InRoutesFilterRef specifies route filtering configuration for the BGP neighbor in 'IN' + // direction. It is the reference to the prefix list, indicating which routes to filter for IN + // direction. Not specifying a value will be treated as "no IN route filters". + InRoutesFilterRef *OpenApiReference `json:"inRoutesFilterRef,omitempty"` + + // OutRoutesFilterRef specifies route filtering configuration for the BGP neighbor in 'OUT' + // direction. It is the reference to the prefix list, indicating which routes to filter for OUT + // direction. Not specifying a value will be treated as "no OUT route filters". + OutRoutesFilterRef *OpenApiReference `json:"outRoutesFilterRef,omitempty"` + + // Specifies the BFD (Bidirectional Forwarding Detection) configuration for failure detection. Not specifying a value + // results in default behavior. + Bfd *EdgeBgpNeighborBfd `json:"bfd,omitempty"` +} + +// EdgeBgpNeighborBfd describes BFD (Bidirectional Forwarding Detection) configuration for failure detection. +type EdgeBgpNeighborBfd struct { + // A flag indicating whether BFD configuration is enabled or not. + Enabled bool `json:"enabled"` + + // BfdInterval specifies the time interval (in milliseconds) between heartbeat packets. + BfdInterval int `json:"bfdInterval,omitempty"` + + // DeclareDeadMultiple specifies number of times heartbeat packet is missed before BFD declares + // that the neighbor is down. + DeclareDeadMultiple int `json:"declareDeadMultiple,omitempty"` + // EdgeBgpIpPrefixList holds BGP IP Prefix List configuration for NSX-T Edge Gateways + +} + type EdgeBgpIpPrefixList struct { // ID is the unique identifier of the entity in URN format. ID string `json:"id,omitempty"`