Skip to content

Commit

Permalink
TM Edge Clusters and QoS (#730)
Browse files Browse the repository at this point in the history
  • Loading branch information
Didainius authored Dec 20, 2024
1 parent 05b2e43 commit 2da52df
Show file tree
Hide file tree
Showing 7 changed files with 423 additions and 28 deletions.
5 changes: 5 additions & 0 deletions .changes/v3.0.0/730-features.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
* Added types `TmEdgeCluster` and `types.TmEdgeCluster` for reading TM Edge Clusters and managing
their QoS settings `VCDClient.GetAllTmEdgeClusters`, `VCDClient.GetTmEdgeClusterByName`,
`VCDClient.GetTmEdgeClusterByNameAndRegionId`, `VCDClient.GetTmEdgeClusterById`,
`VCDClient.TmSyncEdgeClusters`, `TmEdgeCluster.Update`, `TmEdgeCluster.Delete`,
`TmEdgeCluster.GetTransportNodeStatus` [GH-730]
1 change: 1 addition & 0 deletions govcd/api_vcd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ type TestConfig struct {
NsxtManagerUsername string `yaml:"nsxtManagerUsername"`
NsxtManagerPassword string `yaml:"nsxtManagerPassword"`
NsxtManagerUrl string `yaml:"nsxtManagerUrl"`
NsxtEdgeCluster string `yaml:"nsxtEdgeCluster"`
NsxtTier0Gateway string `yaml:"nsxtTier0Gateway"`
} `yaml:"tm,omitempty"`
VCD struct {
Expand Down
31 changes: 17 additions & 14 deletions govcd/openapi_endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,20 +157,23 @@ var endpointMinApiVersions = map[string]string{
types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointNsxtTier0RouterInterfaces: "38.0",

// VCF
types.OpenApiPathVcf + types.OpenApiEndpointRegionStoragePolicies: "40.0",
types.OpenApiPathVcf + types.OpenApiEndpointStorageClasses: "40.0",
types.OpenApiPathVcf + types.OpenApiEndpointContentLibraries: "40.0",
types.OpenApiPathVcf + types.OpenApiEndpointContentLibraryItems: "40.0",
types.OpenApiPathVcf + types.OpenApiEndpointContentLibraryItemFiles: "40.0",
types.OpenApiPathVcf + types.OpenApiEndpointNsxManagers: "40.0",
types.OpenApiPathVcf + types.OpenApiEndpointRegions: "40.0",
types.OpenApiPathVcf + types.OpenApiEndpointSupervisors: "40.0",
types.OpenApiPathVcf + types.OpenApiEndpointSupervisorZones: "40.0",
types.OpenApiPathVcf + types.OpenApiEndpointZones: "40.0",
types.OpenApiPathVcf + types.OpenApiEndpointTmVdcs: "40.0",
types.OpenApiPathVcf + types.OpenApiEndpointTmIpSpaces: "40.0",
types.OpenApiPathVcf + types.OpenApiEndpointTmProviderGateways: "40.0",
types.OpenApiPathVcf + types.OpenApiEndpointTmIpSpaceAssociations: "40.0",
types.OpenApiPathVcf + types.OpenApiEndpointRegionStoragePolicies: "40.0",
types.OpenApiPathVcf + types.OpenApiEndpointStorageClasses: "40.0",
types.OpenApiPathVcf + types.OpenApiEndpointContentLibraries: "40.0",
types.OpenApiPathVcf + types.OpenApiEndpointContentLibraryItems: "40.0",
types.OpenApiPathVcf + types.OpenApiEndpointContentLibraryItemFiles: "40.0",
types.OpenApiPathVcf + types.OpenApiEndpointNsxManagers: "40.0",
types.OpenApiPathVcf + types.OpenApiEndpointRegions: "40.0",
types.OpenApiPathVcf + types.OpenApiEndpointSupervisors: "40.0",
types.OpenApiPathVcf + types.OpenApiEndpointSupervisorZones: "40.0",
types.OpenApiPathVcf + types.OpenApiEndpointZones: "40.0",
types.OpenApiPathVcf + types.OpenApiEndpointTmVdcs: "40.0",
types.OpenApiPathVcf + types.OpenApiEndpointTmIpSpaces: "40.0",
types.OpenApiPathVcf + types.OpenApiEndpointTmProviderGateways: "40.0",
types.OpenApiPathVcf + types.OpenApiEndpointTmIpSpaceAssociations: "40.0",
types.OpenApiPathVcf + types.OpenApiEndpointTmEdgeClusters: "40.0",
types.OpenApiPathVcf + types.OpenApiEndpointTmEdgeClusterTransportNodeStatus: "40.0",
types.OpenApiPathVcf + types.OpenApiEndpointTmEdgeClustersSync: "40.0",
}

// endpointElevatedApiVersions endpoint elevated API versions
Expand Down
172 changes: 172 additions & 0 deletions govcd/tm_edge_cluster.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package govcd

import (
"fmt"
"net/url"

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

const labelTmEdgeCluster = "TM Edge Cluster"
const labelTmEdgeClusterSync = "TM Edge Cluster Sync"
const labelTmEdgeClusterTransportNodeStatus = "TM Edge Cluster Transport Node Status"

// TmEdgeCluster manages read operations for NSX-T Edge Clusters and their QoS settings
type TmEdgeCluster struct {
TmEdgeCluster *types.TmEdgeCluster
vcdClient *VCDClient
}

// wrap is a hidden helper that facilitates the usage of a generic CRUD function
//
//lint:ignore U1000 this method is used in generic functions, but annoys staticcheck
func (g TmEdgeCluster) wrap(inner *types.TmEdgeCluster) *TmEdgeCluster {
g.TmEdgeCluster = inner
return &g
}

// GetAllTmEdgeClusters retrieves all TM Edge Clusters with an optional filter
func (vcdClient *VCDClient) GetAllTmEdgeClusters(queryParameters url.Values) ([]*TmEdgeCluster, error) {
c := crudConfig{
entityLabel: labelTmEdgeCluster,
endpoint: types.OpenApiPathVcf + types.OpenApiEndpointTmEdgeClusters,
queryParameters: queryParameters,
requiresTm: true,
}

outerType := TmEdgeCluster{vcdClient: vcdClient}
return getAllOuterEntities(&vcdClient.Client, outerType, c)
}

// GetTmEdgeClusterByName retrieves TM Edge Cluster by Name
func (vcdClient *VCDClient) GetTmEdgeClusterByName(name string) (*TmEdgeCluster, error) {
if name == "" {
return nil, fmt.Errorf("%s lookup requires name", labelTmEdgeCluster)
}

queryParams := url.Values{}
queryParams.Add("filter", "name=="+name)

filteredEntities, err := vcdClient.GetAllTmEdgeClusters(queryParams)
if err != nil {
return nil, err
}

singleEntity, err := oneOrError("name", name, filteredEntities)
if err != nil {
return nil, err
}

return vcdClient.GetTmEdgeClusterById(singleEntity.TmEdgeCluster.ID)
}

// GetTmEdgeClusterByNameAndRegionId retrieves TM Edge Cluster by Name and a Region ID
func (vcdClient *VCDClient) GetTmEdgeClusterByNameAndRegionId(name, regionId string) (*TmEdgeCluster, error) {
if name == "" {
return nil, fmt.Errorf("%s lookup requires name", labelTmEdgeCluster)
}

if regionId == "" {
return nil, fmt.Errorf("%s lookup requires %s ID", labelTmEdgeCluster, labelRegion)
}

queryParams := url.Values{}
queryParams.Add("filter", "name=="+name)
queryParams = queryParameterFilterAnd(fmt.Sprintf("regionRef.id==%s", regionId), queryParams)

filteredEntities, err := vcdClient.GetAllTmEdgeClusters(queryParams)
if err != nil {
return nil, err
}

singleEntity, err := oneOrError("name", name, filteredEntities)
if err != nil {
return nil, err
}

return vcdClient.GetTmEdgeClusterById(singleEntity.TmEdgeCluster.ID)
}

// GetTmEdgeClusterById retrieves TM Edge Cluster by ID
func (vcdClient *VCDClient) GetTmEdgeClusterById(id string) (*TmEdgeCluster, error) {
c := crudConfig{
entityLabel: labelTmEdgeCluster,
endpoint: types.OpenApiPathVcf + types.OpenApiEndpointTmEdgeClusters,
endpointParams: []string{id},
requiresTm: true,
}

outerType := TmEdgeCluster{vcdClient: vcdClient}
return getOuterEntity(&vcdClient.Client, outerType, c)
}

// TmSyncEdgeClusters triggers a global sync operation that re-reads available Edge Clusters in all
// configured NSX-T Managers
func (vcdClient *VCDClient) TmSyncEdgeClusters() error {
client := vcdClient.Client
endpoint := types.OpenApiPathVcf + types.OpenApiEndpointTmEdgeClustersSync

apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
if err != nil {
return err
}

urlRef, err := client.OpenApiBuildEndpoint(endpoint)
if err != nil {
return err
}

task, err := client.OpenApiPostItemAsync(apiVersion, urlRef, nil, nil)
if err != nil {
return fmt.Errorf("error performing %s: %s", labelTmEdgeClusterSync, err)
}

err = task.WaitTaskCompletion()
if err != nil {
return fmt.Errorf("error performing %s: %s", labelTmEdgeClusterSync, err)
}

return nil
}

// Update TM Edge Cluster with a given config
// Note. Only `DefaultQosConfig` structure is updatable
func (e *TmEdgeCluster) Update(TmEdgeClusterConfig *types.TmEdgeCluster) (*TmEdgeCluster, error) {
c := crudConfig{
entityLabel: labelTmEdgeCluster,
endpoint: types.OpenApiPathVcf + types.OpenApiEndpointTmEdgeClusters,
endpointParams: []string{e.TmEdgeCluster.ID},
requiresTm: true,
}
outerType := TmEdgeCluster{vcdClient: e.vcdClient}
return updateOuterEntity(&e.vcdClient.Client, outerType, c, TmEdgeClusterConfig)
}

// Delete removes the QoS configuration for a given TM Edge Cluster as the Edge Cluster itself is
// not removable
func (e *TmEdgeCluster) Delete() error {
if e.TmEdgeCluster == nil {
return fmt.Errorf("nil %s", labelTmEdgeCluster)
}
e.TmEdgeCluster.DefaultQosConfig.EgressProfile = nil
e.TmEdgeCluster.DefaultQosConfig.IngressProfile = nil

_, err := e.Update(e.TmEdgeCluster)
if err != nil {
return fmt.Errorf("error removing QoS configuration for %s: %s", labelTmEdgeCluster, err)
}

return nil
}

// GetTransportNodeStatus retrieves status of all member transport nodes of specified Edge Cluster
func (e *TmEdgeCluster) GetTransportNodeStatus() ([]*types.TmEdgeClusterTransportNodeStatus, error) {
c := crudConfig{
entityLabel: labelTmEdgeClusterTransportNodeStatus,
endpoint: types.OpenApiPathVcf + types.OpenApiEndpointTmEdgeClusterTransportNodeStatus,
endpointParams: []string{e.TmEdgeCluster.ID},
requiresTm: true,
}

return getAllInnerEntities[types.TmEdgeClusterTransportNodeStatus](&e.vcdClient.Client, c)
}
107 changes: 107 additions & 0 deletions govcd/tm_edge_cluster_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
//go:build tm || functional || ALL

/*
* Copyright 2024 VMware, Inc. All rights reserved. Licensed under the Apache v2 License.
*/

package govcd

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

func (vcd *TestVCD) Test_TmEdgeCluster(check *C) {
skipNonTm(vcd, check)
sysadminOnly(vcd, check)

vc, vcCleanup := getOrCreateVCenter(vcd, check)
defer vcCleanup()
nsxtManager, nsxtManagerCleanup := getOrCreateNsxtManager(vcd, check)
defer nsxtManagerCleanup()
supervisor, err := vc.GetSupervisorByName(vcd.config.Tm.VcenterSupervisor)
check.Assert(err, IsNil)
region, regionCleanup := getOrCreateRegion(vcd, nsxtManager, supervisor, check)
defer regionCleanup()

// Sync
err = vcd.client.TmSyncEdgeClusters()
check.Assert(err, IsNil)

// Lookup
allClusters, err := vcd.client.GetAllTmEdgeClusters(nil)
check.Assert(err, IsNil)
check.Assert(allClusters, NotNil)
check.Assert(len(allClusters) > 0, Equals, true)

ecByName, err := vcd.client.GetTmEdgeClusterByName(vcd.config.Tm.NsxtEdgeCluster)
check.Assert(err, IsNil)
check.Assert(ecByName, NotNil)
check.Assert(ecByName.TmEdgeCluster.Name, Equals, vcd.config.Tm.NsxtEdgeCluster)

ecById, err := vcd.client.GetTmEdgeClusterById(ecByName.TmEdgeCluster.ID)
check.Assert(err, IsNil)
check.Assert(ecById, NotNil)
check.Assert(ecById.TmEdgeCluster, DeepEquals, ecByName.TmEdgeCluster)

ecByNameAndRegionId, err := vcd.client.GetTmEdgeClusterByNameAndRegionId(vcd.config.Tm.NsxtEdgeCluster, region.Region.ID)
check.Assert(err, IsNil)
check.Assert(ecByNameAndRegionId, NotNil)
check.Assert(ecByNameAndRegionId.TmEdgeCluster, DeepEquals, ecById.TmEdgeCluster)

ecByNameAndWrongRegionId, err := vcd.client.GetTmEdgeClusterByNameAndRegionId(vcd.config.Tm.NsxtEdgeCluster, "urn:vcloud:region:167d34b3-0000-0000-0000-a388505e6102")
check.Assert(ContainsNotFound(err), Equals, true)
check.Assert(ecByNameAndWrongRegionId, IsNil)

// Qos modifications
qosUpdate := &types.TmEdgeCluster{
ID: ecByName.TmEdgeCluster.ID,
Name: ecByName.TmEdgeCluster.Name,
RegionRef: ecByName.TmEdgeCluster.RegionRef,
DefaultQosConfig: types.TmEdgeClusterDefaultQosConfig{
IngressProfile: &types.TmEdgeClusterQosProfile{
CommittedBandwidthMbps: 100,
BurstSizeBytes: 5,
Type: "DEFAULT",
},
EgressProfile: &types.TmEdgeClusterQosProfile{
CommittedBandwidthMbps: 200,
BurstSizeBytes: 6,
Type: "DEFAULT",
},
},
}

updatedQos, err := ecById.Update(qosUpdate)
check.Assert(err, IsNil)
check.Assert(updatedQos.TmEdgeCluster.DefaultQosConfig.EgressProfile.CommittedBandwidthMbps, DeepEquals, qosUpdate.DefaultQosConfig.EgressProfile.CommittedBandwidthMbps)
check.Assert(updatedQos.TmEdgeCluster.DefaultQosConfig.EgressProfile.BurstSizeBytes, DeepEquals, qosUpdate.DefaultQosConfig.EgressProfile.BurstSizeBytes)
check.Assert(updatedQos.TmEdgeCluster.DefaultQosConfig.EgressProfile.Type, DeepEquals, qosUpdate.DefaultQosConfig.EgressProfile.Type)
check.Assert(updatedQos.TmEdgeCluster.DefaultQosConfig.EgressProfile.ID, Not(Equals), "")
check.Assert(updatedQos.TmEdgeCluster.DefaultQosConfig.EgressProfile.Name, Not(Equals), "")

check.Assert(updatedQos.TmEdgeCluster.DefaultQosConfig.IngressProfile.CommittedBandwidthMbps, DeepEquals, qosUpdate.DefaultQosConfig.IngressProfile.CommittedBandwidthMbps)
check.Assert(updatedQos.TmEdgeCluster.DefaultQosConfig.IngressProfile.BurstSizeBytes, DeepEquals, qosUpdate.DefaultQosConfig.IngressProfile.BurstSizeBytes)
check.Assert(updatedQos.TmEdgeCluster.DefaultQosConfig.IngressProfile.Type, DeepEquals, qosUpdate.DefaultQosConfig.IngressProfile.Type)
check.Assert(updatedQos.TmEdgeCluster.DefaultQosConfig.IngressProfile.ID, Not(Equals), "")
check.Assert(updatedQos.TmEdgeCluster.DefaultQosConfig.IngressProfile.Name, Not(Equals), "")

// Remove QoS configuration
err = updatedQos.Delete()
check.Assert(err, IsNil)

afterQosRemoval, err := vcd.client.GetTmEdgeClusterByName(vcd.config.Tm.NsxtEdgeCluster)
check.Assert(err, IsNil)
check.Assert(afterQosRemoval, NotNil)
check.Assert(afterQosRemoval.TmEdgeCluster.DefaultQosConfig.EgressProfile, IsNil)
check.Assert(afterQosRemoval.TmEdgeCluster.DefaultQosConfig.IngressProfile, IsNil)

// Check Transport Node endpoint
tn, err := afterQosRemoval.GetTransportNodeStatus()
check.Assert(err, IsNil)
check.Assert(tn, NotNil)
check.Assert(len(tn) > 0, Equals, true)
for i := range tn {
check.Assert(tn[i].NodeName != "", Equals, true)
}
}
31 changes: 17 additions & 14 deletions types/v56/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -520,20 +520,23 @@ const (
// OpenAPI Org
OpenApiEndpointOrgs = "orgs/"

OpenApiEndpointRegionStoragePolicies = "regionStoragePolicies/"
OpenApiEndpointStorageClasses = "storageClasses/"
OpenApiEndpointContentLibraries = "contentLibraries/"
OpenApiEndpointContentLibraryItems = "contentLibraryItems/"
OpenApiEndpointContentLibraryItemFiles = "contentLibraryItems/%s/files"
OpenApiEndpointNsxManagers = "nsxManagers/"
OpenApiEndpointRegions = "regions/"
OpenApiEndpointSupervisors = "supervisors/"
OpenApiEndpointSupervisorZones = "supervisorZones/"
OpenApiEndpointZones = "zones/"
OpenApiEndpointTmVdcs = "virtualDatacenters/"
OpenApiEndpointTmIpSpaces = "ipSpaces/"
OpenApiEndpointTmProviderGateways = "providerGateways/"
OpenApiEndpointTmIpSpaceAssociations = "ipSpaceAssociations/"
OpenApiEndpointRegionStoragePolicies = "regionStoragePolicies/"
OpenApiEndpointStorageClasses = "storageClasses/"
OpenApiEndpointContentLibraries = "contentLibraries/"
OpenApiEndpointContentLibraryItems = "contentLibraryItems/"
OpenApiEndpointContentLibraryItemFiles = "contentLibraryItems/%s/files"
OpenApiEndpointNsxManagers = "nsxManagers/"
OpenApiEndpointRegions = "regions/"
OpenApiEndpointSupervisors = "supervisors/"
OpenApiEndpointSupervisorZones = "supervisorZones/"
OpenApiEndpointZones = "zones/"
OpenApiEndpointTmVdcs = "virtualDatacenters/"
OpenApiEndpointTmIpSpaces = "ipSpaces/"
OpenApiEndpointTmProviderGateways = "providerGateways/"
OpenApiEndpointTmIpSpaceAssociations = "ipSpaceAssociations/"
OpenApiEndpointTmEdgeClusters = "edgeClusters/"
OpenApiEndpointTmEdgeClusterTransportNodeStatus = "edgeClusters/%s/transportNodesStatus"
OpenApiEndpointTmEdgeClustersSync = "edgeClusters/sync"
)

// Header keys to run operations in tenant context
Expand Down
Loading

0 comments on commit 2da52df

Please sign in to comment.