diff --git a/.changes/v2.16.0/478-features.md b/.changes/v2.16.0/478-features.md new file mode 100644 index 000000000..f6e7f4901 --- /dev/null +++ b/.changes/v2.16.0/478-features.md @@ -0,0 +1 @@ +* Add NSX-T Edge Gateway methods `NsxtEdgeGateway.GetNsxtRouteAdvertisement`, `NsxtEdgeGateway.GetNsxtRouteAdvertisementWithContext`, `NsxtEdgeGateway.UpdateNsxtRouteAdvertisement`, `NsxtEdgeGateway.UpdateNsxtRouteAdvertisementWithContext`, `NsxtEdgeGateway.DeleteNsxtRouteAdvertisement` and `NsxtEdgeGateway.DeleteNsxtRouteAdvertisementWithContext` that allows to manage NSX-T route advertisement [GH-478] diff --git a/govcd/catalog_test.go b/govcd/catalog_test.go index e627be318..2310f02c0 100644 --- a/govcd/catalog_test.go +++ b/govcd/catalog_test.go @@ -964,12 +964,12 @@ func (vcd *TestVCD) Test_CatalogQueryMediaList(check *C) { check.Assert(medias[0].Name, Equals, vcd.config.Media.Media) } -// Tests System function UploadMediaImage by checking if provided UDF type standard iso file uploaded. +// Tests System function UploadMediaImage by using provided ISO file of UDF type. func (vcd *TestVCD) Test_CatalogUploadMediaImageWihUdfTypeIso(check *C) { fmt.Printf("Running: %s\n", check.TestName()) if vcd.config.Media.MediaUdfTypePath == "" { - check.Skip("Skipping test because no UDF type iso path given") + check.Skip("Skipping test because no UDF type ISO path was given") } catalog, org := findCatalog(vcd, check, vcd.config.VCD.Catalog.Name) diff --git a/govcd/nsxt_route_advertisement.go b/govcd/nsxt_route_advertisement.go new file mode 100644 index 000000000..f3462b28a --- /dev/null +++ b/govcd/nsxt_route_advertisement.go @@ -0,0 +1,127 @@ +/* + * Copyright 2022 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. + */ + +package govcd + +import ( + "fmt" + "github.com/vmware/go-vcloud-director/v2/types/v56" +) + +// GetNsxtRouteAdvertisementWithContext retrieves the list of subnets that will be advertised so that the Edge Gateway can route +// out to the connected external network. +func (egw *NsxtEdgeGateway) GetNsxtRouteAdvertisementWithContext(useTenantContext bool) (*types.RouteAdvertisement, error) { + err := checkSanityNsxtEdgeGatewayRouteAdvertisement(egw) + if err != nil { + return nil, err + } + + endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointNsxtRouteAdvertisement + + highestApiVersion, err := egw.client.getOpenApiHighestElevatedVersion(endpoint) + if err != nil { + return nil, err + } + + urlRef, err := egw.client.OpenApiBuildEndpoint(fmt.Sprintf(endpoint, egw.EdgeGateway.ID)) + if err != nil { + return nil, err + } + + var tenantContextHeaders map[string]string + if useTenantContext { + tenantContext, err := egw.getTenantContext() + if err != nil { + return nil, err + } + + tenantContextHeaders = getTenantContextHeader(tenantContext) + } + + routeAdvertisement := &types.RouteAdvertisement{} + err = egw.client.OpenApiGetItem(highestApiVersion, urlRef, nil, routeAdvertisement, tenantContextHeaders) + if err != nil { + return nil, err + } + + return routeAdvertisement, nil +} + +// GetNsxtRouteAdvertisement method is the same as GetNsxtRouteAdvertisementWithContext but sending TenantContext by default +func (egw *NsxtEdgeGateway) GetNsxtRouteAdvertisement() (*types.RouteAdvertisement, error) { + return egw.GetNsxtRouteAdvertisementWithContext(true) +} + +// UpdateNsxtRouteAdvertisementWithContext updates the list of subnets that will be advertised so that the Edge Gateway can route +// out to the connected external network. +func (egw *NsxtEdgeGateway) UpdateNsxtRouteAdvertisementWithContext(enable bool, subnets []string, useTenantContext bool) (*types.RouteAdvertisement, error) { + err := checkSanityNsxtEdgeGatewayRouteAdvertisement(egw) + if err != nil { + return nil, err + } + + endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointNsxtRouteAdvertisement + + highestApiVersion, err := egw.client.getOpenApiHighestElevatedVersion(endpoint) + if err != nil { + return nil, err + } + + urlRef, err := egw.client.OpenApiBuildEndpoint(fmt.Sprintf(endpoint, egw.EdgeGateway.ID)) + if err != nil { + return nil, err + } + + var tenantContextHeaders map[string]string + if useTenantContext { + tenantContext, err := egw.getTenantContext() + if err != nil { + return nil, err + } + + tenantContextHeaders = getTenantContextHeader(tenantContext) + } + + routeAdvertisement := &types.RouteAdvertisement{ + Enable: enable, + Subnets: subnets, + } + + err = egw.client.OpenApiPutItem(highestApiVersion, urlRef, nil, routeAdvertisement, nil, tenantContextHeaders) + if err != nil { + return nil, err + } + + return egw.GetNsxtRouteAdvertisementWithContext(useTenantContext) +} + +// UpdateNsxtRouteAdvertisement method is the same as UpdateNsxtRouteAdvertisementWithContext but sending TenantContext by default +func (egw *NsxtEdgeGateway) UpdateNsxtRouteAdvertisement(enable bool, subnets []string) (*types.RouteAdvertisement, error) { + return egw.UpdateNsxtRouteAdvertisementWithContext(enable, subnets, true) +} + +// DeleteNsxtRouteAdvertisementWithContext deletes the list of subnets that will be advertised. +func (egw *NsxtEdgeGateway) DeleteNsxtRouteAdvertisementWithContext(useTenantContext bool) error { + _, err := egw.UpdateNsxtRouteAdvertisementWithContext(false, []string{}, useTenantContext) + return err +} + +// DeleteNsxtRouteAdvertisement method is the same as DeleteNsxtRouteAdvertisementWithContext but sending TenantContext by default +func (egw *NsxtEdgeGateway) DeleteNsxtRouteAdvertisement() error { + return egw.DeleteNsxtRouteAdvertisementWithContext(true) +} + +// checkSanityNsxtEdgeGatewayRouteAdvertisement function performs some checks to *NsxtEdgeGateway parameter and returns error +// if something is wrong. It is useful with methods NsxtEdgeGateway.[Get/Update/Delete]NsxtRouteAdvertisement +func checkSanityNsxtEdgeGatewayRouteAdvertisement(egw *NsxtEdgeGateway) error { + if egw.EdgeGateway == nil { + return fmt.Errorf("the EdgeGateway pointer is nil. Please initialize it first before using this method") + } + + if egw.EdgeGateway.ID == "" { + return fmt.Errorf("the EdgeGateway ID is empty. Please initialize it first before using this method") + } + + return nil +} diff --git a/govcd/nsxt_route_advertisement_test.go b/govcd/nsxt_route_advertisement_test.go new file mode 100644 index 000000000..c6cd69240 --- /dev/null +++ b/govcd/nsxt_route_advertisement_test.go @@ -0,0 +1,75 @@ +//go:build network || nsxt || functional || openapi || ALL +// +build network nsxt functional openapi ALL + +/* + * Copyright 2022 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. + */ + +package govcd + +import ( + "fmt" + "github.com/vmware/go-vcloud-director/v2/types/v56" + . "gopkg.in/check.v1" +) + +func (vcd *TestVCD) Test_NsxtEdgeRouteAdvertisement(check *C) { + skipNoNsxtConfiguration(vcd, check) + skipOpenApiEndpointTest(vcd, check, types.OpenApiPathVersion1_0_0+types.OpenApiEndpointNsxtRouteAdvertisement) + + 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) + + // Make sure we are using a dedicated Tier-0 gateway (otherwise route advertisement won't be available) + edge, err = setDedicateTier0Gateway(edge, true) + check.Assert(err, IsNil) + check.Assert(edge, NotNil) + + // Make sure that things get back to normal when the test is done + defer setDedicateTier0Gateway(edge, false) + + network1 := "192.168.1.0/24" + network2 := "192.168.2.0/24" + networksToAdvertise := []string{network1, network2} // Sample networks to advertise + + // Test UpdateNsxtRouteAdvertisement + nsxtEdgeRouteAdvertisement, err := edge.UpdateNsxtRouteAdvertisement(true, networksToAdvertise) + check.Assert(err, IsNil) + check.Assert(nsxtEdgeRouteAdvertisement, NotNil) + check.Assert(nsxtEdgeRouteAdvertisement.Enable, Equals, true) + check.Assert(len(nsxtEdgeRouteAdvertisement.Subnets), Equals, 2) + check.Assert(checkNetworkInSubnetsSlice(network1, networksToAdvertise), IsNil) + check.Assert(checkNetworkInSubnetsSlice(network2, networksToAdvertise), IsNil) + + // Test DeleteNsxtRouteAdvertisement + err = edge.DeleteNsxtRouteAdvertisement() + check.Assert(err, IsNil) + nsxtEdgeRouteAdvertisement, err = edge.GetNsxtRouteAdvertisement() + check.Assert(err, IsNil) + check.Assert(nsxtEdgeRouteAdvertisement, NotNil) + check.Assert(nsxtEdgeRouteAdvertisement.Enable, Equals, false) + check.Assert(len(nsxtEdgeRouteAdvertisement.Subnets), Equals, 0) +} + +func checkNetworkInSubnetsSlice(network string, subnets []string) error { + for _, subnet := range subnets { + if subnet == network { + return nil + } + } + return fmt.Errorf("network %s is not within the slice provided", network) +} + +func setDedicateTier0Gateway(edgeGateway *NsxtEdgeGateway, dedicate bool) (*NsxtEdgeGateway, error) { + edgeGateway.EdgeGateway.EdgeGatewayUplinks[0].Dedicated = dedicate + edgeGateway, err := edgeGateway.Update(edgeGateway.EdgeGateway) + if err != nil { + return nil, err + } + + return edgeGateway, nil +} diff --git a/govcd/openapi_endpoints.go b/govcd/openapi_endpoints.go index 234d2b679..d3d6dc01e 100644 --- a/govcd/openapi_endpoints.go +++ b/govcd/openapi_endpoints.go @@ -49,6 +49,7 @@ var endpointMinApiVersions = map[string]string{ types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointVdcGroupsDfwPolicies: "35.0", // VCD 10.2+ types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointVdcGroupsDfwDefaultPolicies: "35.0", // VCD 10.2+ types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointSecurityTags: "36.0", // VCD 10.3+ + types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointNsxtRouteAdvertisement: "34.0", // VCD 10.1+ // NSX-T ALB (Advanced/AVI Load Balancer) support was introduced in 10.2 types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointAlbController: "35.0", // VCD 10.2+ diff --git a/govcd/tenant_context.go b/govcd/tenant_context.go index 6f540cd1d..4e8e90f40 100644 --- a/govcd/tenant_context.go +++ b/govcd/tenant_context.go @@ -183,3 +183,15 @@ func (vdcGroup *VdcGroup) getTenantContext() (*TenantContext, error) { } return org.tenantContext() } + +func (egw *NsxtEdgeGateway) getTenantContext() (*TenantContext, error) { + if egw != nil && egw.EdgeGateway.Org != nil { + if egw.EdgeGateway.Org.Name == "" || egw.EdgeGateway.Org.ID == "" { + return nil, fmt.Errorf("either parent NsxtEdgeGateway Org name or ID is empty and both must be set. Org name is [%s] and Org ID is [%s]", egw.EdgeGateway.Org.Name, egw.EdgeGateway.Org.ID) + } + + return &TenantContext{OrgId: egw.EdgeGateway.Org.ID, OrgName: egw.EdgeGateway.Org.Name}, nil + } + + return nil, fmt.Errorf("NsxtEdgeGateway is not fully initialized. Please initialize it before using this method") +} diff --git a/types/v56/constants.go b/types/v56/constants.go index d82b6915d..a8e42facf 100644 --- a/types/v56/constants.go +++ b/types/v56/constants.go @@ -373,6 +373,7 @@ const ( OpenApiEndpointVdcGroupsDfwRules = "vdcGroups/%s/dfwPolicies/%s/rules" OpenApiEndpointNetworkContextProfiles = "networkContextProfiles" OpenApiEndpointSecurityTags = "securityTags" + OpenApiEndpointNsxtRouteAdvertisement = "edgeGateways/%s/routing/advertisement" // NSX-T ALB related endpoints diff --git a/types/v56/nsxt_types.go b/types/v56/nsxt_types.go index 642fb0c7a..2a95d08bf 100644 --- a/types/v56/nsxt_types.go +++ b/types/v56/nsxt_types.go @@ -1244,3 +1244,13 @@ type EntitySecurityTags struct { // Tags is the list of tags. The value is case-agnostic and will be converted to lower-case. Tags []string `json:"tags"` } + +// RouteAdvertisement lists the subnets that will be advertised so that the Edge Gateway can route out to the +// connected external network. +type RouteAdvertisement struct { + // Enable if true, means that the subnets will be advertised. + Enable bool `json:"enable"` + // Subnets is the list of subnets that will be advertised so that the Edge Gateway can route out to the connected + // external network. + Subnets []string `json:"subnets"` +}