Skip to content

Commit

Permalink
Provider VDC CRUD (#580)
Browse files Browse the repository at this point in the history
* Add Provider VDC creation functions
* Add data structure for Provider VDC creation
* Convert simple queries to cumulativeQuery calls
* Add test for Provider VDC creation
* Add resource pool retrieval functions
* Add vCenter retrieval functions
* Add provider network pool data structures
* Add network pool retrieval methods
* Add resource pools to TestConfig structure
* Add PVDC methods Delete, IsEnabled, Disable, Enable
* Add Update method to provider VDC
* Add vsphere_storage_profile methods
* Add second storage profile to TestConfig
* Add structure for storage profiles
* Fix optimistic tests with network pools
  When the Provider VDC uses a network pool from a NSX-T manager
  that contains more than one network pool, it acquires all network pools
  from that manager, even if the second network pool was created after the
  provider VDC
* Add PvDC tests for all network pool handlings

Signed-off-by: Giuseppe Maxia <[email protected]>
  • Loading branch information
dataclouder authored Jul 12, 2023
1 parent be0efa9 commit 91abc52
Show file tree
Hide file tree
Showing 23 changed files with 1,757 additions and 110 deletions.
1 change: 1 addition & 0 deletions .changes/v2.21.0/580-bug-fixes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* Fixed [Issue #1066](https://github.com/vmware/terraform-provider-vcd/issues/1066) - Not possible to handle more than 128 storage profiles [GH-580]
13 changes: 13 additions & 0 deletions .changes/v2.21.0/580-features.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
* Added method `VCDClient.QueryNsxtManagerByHref` to retrieve a NSX-T manager by its ID/HREF [GH-580]
* Added method `VCDClient.CreateProviderVdc` to create a Provider VDC [GH-580]
* Added method `VCDClient.ResourcePoolsFromIds` to convert list of IDs to resource pools [GH-580]
* Added `ProviderVdcExtended` methods `AddResourcePools`, `AddStorageProfiles`, `Delete`, `DeleteResourcePools`,`DeleteStorageProfiles`,`Disable`,`Enable`,`GetResourcePools`,`IsEnabled`,`Rename`,`Update` to fully manage a provider VDC [GH-580]
* Added method `NetworkPool.GetOpenApiUrl` to generate the full URL of a network pool [GH-580]
* Added `ResourcePool` methods `GetAvailableHardwareVersions` and `GetDefaultHardwareVersion` to get hardware versions [GH-580]
* Added `VCDClient` method `GetAllResourcePools` to retrieve all resource pools regardless of vCenter affiliation [GH-580]
* Added `VCDClient` method `GetAllVcenters` to retrieve all vCenters [GH-580]
* Added `VCDClient` methods `GetNetworkPoolById`,`GetNetworkPoolByName`,`GetNetworkPoolSummaries` to retrieve network pools [GH-580]
* Added `VCDClient` methods `GetVcenterById`,`GetVcenterByName` to retrieve vCenters [GH-580]
* Added `VCenter` methods `GetAllResourcePools`,`VCenter.GetResourcePoolById`,`VCenter.GetResourcePoolByName` to retrieve resource pools [GH-580]
* Added `VCenter` methods `GetAllStorageProfiles`,`GetStorageProfileById`,`GetStorageProfileByName` to retrieve storage profiles [GH-580]
* Added method `VCenter.GetVimServerUrl` to retrieve the full URL of a vCenter within a VCD [GH-580]
4 changes: 2 additions & 2 deletions govcd/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,12 +238,12 @@ func (client *Client) newRequest(params map[string]string, notEncodedParams map[
req.Header.Add("Authorization", "bearer "+client.VCDToken)
}

// Merge in additional headers before logging if any where specified in additionalHeader
// Merge in additional headers before logging if anywhere specified in additionalHeader
// parameter
if len(additionalHeader) > 0 {
for headerName, headerValueSlice := range additionalHeader {
for _, singleHeaderValue := range headerValueSlice {
req.Header.Add(headerName, singleHeaderValue)
req.Header.Set(headerName, singleHeaderValue)
}
}
}
Expand Down
69 changes: 69 additions & 0 deletions govcd/api_json.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright 2023 VMware, Inc. All rights reserved. Licensed under the Apache v2 License.
*/

package govcd

import (
"encoding/json"
"fmt"
"github.com/vmware/go-vcloud-director/v2/types/v56"
"github.com/vmware/go-vcloud-director/v2/util"
"io"
"net/http"
"net/url"
"strings"
)

// executeJsonRequest is a wrapper around regular API call operations, similar to client.ExecuteRequest, but with JSON payback
// Returns a http.Response object, which, in case of success, has its body still unread
// Caller function has the responsibility for closing the response body
func (client Client) executeJsonRequest(href, httpMethod string, inputStructure any, errorMessage string) (*http.Response, error) {

text, err := json.MarshalIndent(inputStructure, " ", " ")
if err != nil {
return nil, err
}
requestHref, err := url.Parse(href)
if err != nil {
return nil, err
}

var resp *http.Response
body := strings.NewReader(string(text))
apiVersion := client.APIVersion
headAccept := http.Header{}
headAccept.Set("Accept", fmt.Sprintf("application/*+json;version=%s", apiVersion))
headAccept.Set("Content-Type", "application/*+json")
request := client.newRequest(nil, nil, httpMethod, *requestHref, body, apiVersion, headAccept)
resp, err = client.Http.Do(request)
if err != nil {
return nil, fmt.Errorf(errorMessage, err)
}

if resp.StatusCode < 200 || resp.StatusCode >= 300 {
body, _ := io.ReadAll(resp.Body)
util.ProcessResponseOutput(util.CallFuncName(), resp, string(body))
var jsonError types.OpenApiError
err = json.Unmarshal(body, &jsonError)
// By default, we return the whole response body as error message. This may also contain the stack trace
message := string(body)
// if the body contains a valid JSON representation of the error, we return a more agile message, using the
// exposed fields, and hiding the stack trace from view
if err == nil {
message = fmt.Sprintf("%s - %s", jsonError.MinorErrorCode, jsonError.Message)
}
util.ProcessResponseOutput(util.CallFuncName(), resp, string(body))
return resp, fmt.Errorf(errorMessage, message)
}

return checkRespWithErrType(types.BodyTypeJSON, resp, err, &types.Error{})
}

// closeBody is a wrapper function that should be used with "defer" after calling executeJsonRequest
func closeBody(resp *http.Response) {
err := resp.Body.Close()
if err != nil {
util.Logger.Printf("error closing response body - Called by %s: %s\n", util.CallFuncName(), err)
}
}
30 changes: 29 additions & 1 deletion govcd/api_vcd_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build api || openapi || functional || catalog || vapp || gateway || network || org || query || extnetwork || task || vm || vdc || system || disk || lb || lbAppRule || lbAppProfile || lbServerPool || lbServiceMonitor || lbVirtualServer || user || search || nsxv || nsxt || auth || affinity || role || alb || certificate || vdcGroup || metadata || providervdc || rde || uiPlugin || ALL
//go:build api || openapi || functional || catalog || vapp || gateway || network || org || query || extnetwork || task || vm || vdc || system || disk || lb || lbAppRule || lbAppProfile || lbServerPool || lbServiceMonitor || lbVirtualServer || user || search || nsxv || nsxt || auth || affinity || role || alb || certificate || vdcGroup || metadata || providervdc || rde || vsphere || uiPlugin || ALL

/*
* Copyright 2022 VMware, Inc. All rights reserved. Licensed under the Apache v2 License.
Expand Down Expand Up @@ -146,6 +146,7 @@ type TestConfig struct {
NsxtProviderVdc struct {
Name string `yaml:"name"`
StorageProfile string `yaml:"storage_profile"`
StorageProfile2 string `yaml:"storage_profile_2"`
NetworkPool string `yaml:"network_pool"`
PlacementPolicyVmGroup string `yaml:"placementPolicyVmGroup,omitempty"`
} `yaml:"nsxt_provider_vdc"`
Expand Down Expand Up @@ -198,6 +199,10 @@ type TestConfig struct {
NsxtAlbServiceEngineGroup string `yaml:"nsxtAlbServiceEngineGroup"`
} `yaml:"nsxt"`
} `yaml:"vcd"`
Vsphere struct {
ResourcePoolForVcd1 string `yaml:"resourcePoolForVcd1,omitempty"`
ResourcePoolForVcd2 string `yaml:"resourcePoolForVcd2,omitempty"`
} `yaml:"vsphere,omitempty"`
Logging struct {
Enabled bool `yaml:"enabled,omitempty"`
LogFileName string `yaml:"logFileName,omitempty"`
Expand Down Expand Up @@ -931,6 +936,29 @@ func (vcd *TestVCD) removeLeftoverEntities(entity CleanupEntity) {
}
vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy)
return
case "provider_vdc":
pvdc, err := vcd.client.GetProviderVdcExtendedByName(entity.Name)
if err != nil {
vcd.infoCleanup(notFoundMsg, entity.EntityType, entity.Name)
return
}
err = pvdc.Disable()
if err != nil {
vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err)
return
}
task, err := pvdc.Delete()
if err != nil {
vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err)
return
}
err = task.WaitTaskCompletion()
if err != nil {
vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err)
return
}
vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy)
return
case "catalogItem":
if entity.Parent == "" {
vcd.infoCleanup("removeLeftoverEntries: [ERROR] No Org provided for catalogItem '%s'\n", strings.Split(entity.Parent, "|")[0])
Expand Down
2 changes: 1 addition & 1 deletion govcd/common_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build api || auth || functional || catalog || vapp || gateway || network || org || query || extnetwork || task || vm || vdc || system || disk || lb || lbAppRule || lbAppProfile || lbServerPool || lbServiceMonitor || lbVirtualServer || user || role || nsxv || nsxt || openapi || affinity || search || alb || certificate || vdcGroup || metadata || providervdc || rde || uiPlugin || ALL
//go:build api || auth || functional || catalog || vapp || gateway || network || org || query || extnetwork || task || vm || vdc || system || disk || lb || lbAppRule || lbAppProfile || lbServerPool || lbServiceMonitor || lbVirtualServer || user || role || nsxv || nsxt || openapi || affinity || search || alb || certificate || vdcGroup || metadata || providervdc || rde || uiPlugin || vsphere || ALL

/*
* Copyright 2021 VMware, Inc. All rights reserved. Licensed under the Apache v2 License.
Expand Down
104 changes: 104 additions & 0 deletions govcd/network_pool.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* Copyright 2023 VMware, Inc. All rights reserved. Licensed under the Apache v2 License.
*/

package govcd

import (
"fmt"
"github.com/vmware/go-vcloud-director/v2/types/v56"
"net/url"
)

type NetworkPool struct {
NetworkPool *types.NetworkPool
vcdClient *VCDClient
}

// GetOpenApiUrl retrieves the full URL of a network pool
func (np NetworkPool) GetOpenApiUrl() (string, error) {
response, err := url.JoinPath(np.vcdClient.sessionHREF.String(), "admin", "extension", "networkPool", np.NetworkPool.Id)
if err != nil {
return "", err
}
return response, nil
}

// GetNetworkPoolSummaries retrieves the list of all available network pools
func (vcdClient *VCDClient) GetNetworkPoolSummaries(queryParameters url.Values) ([]*types.NetworkPool, error) {
client := vcdClient.Client
endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointNetworkPoolSummaries
apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
if err != nil {
return nil, err
}

urlRef, err := client.OpenApiBuildEndpoint(endpoint)
if err != nil {
return nil, err
}
typeResponse := []*types.NetworkPool{{}}
err = client.OpenApiGetAllItems(apiVersion, urlRef, queryParameters, &typeResponse, nil)
if err != nil {
return nil, err
}

return typeResponse, nil
}

// GetNetworkPoolById retrieves Network Pool with a given ID
func (vcdClient *VCDClient) GetNetworkPoolById(id string) (*NetworkPool, error) {
if id == "" {
return nil, fmt.Errorf("network pool lookup requires ID")
}

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

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

response := &NetworkPool{
vcdClient: vcdClient,
NetworkPool: &types.NetworkPool{},
}

err = client.OpenApiGetItem(apiVersion, urlRef, nil, response.NetworkPool, nil)
if err != nil {
return nil, err
}

return response, nil
}

// GetNetworkPoolByName retrieves a network pool with a given name
// Note. It will return an error if multiple network pools exist with the same name
func (vcdClient *VCDClient) GetNetworkPoolByName(name string) (*NetworkPool, error) {
if name == "" {
return nil, fmt.Errorf("network pool lookup requires name")
}

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

filteredNetworkPools, err := vcdClient.GetNetworkPoolSummaries(queryParameters)
if err != nil {
return nil, fmt.Errorf("error getting network pools: %s", err)
}

if len(filteredNetworkPools) == 0 {
return nil, fmt.Errorf("no network pool found with name '%s' - %s", name, ErrorEntityNotFound)
}

if len(filteredNetworkPools) > 1 {
return nil, fmt.Errorf("more than one network pool found with name '%s'", name)
}

return vcdClient.GetNetworkPoolById(filteredNetworkPools[0].Id)
}
50 changes: 50 additions & 0 deletions govcd/network_pool_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//go:build providervdc || functional || ALL

package govcd

import (
"fmt"
"github.com/kr/pretty"
. "gopkg.in/check.v1"
)

func (vcd *TestVCD) Test_GetNetworkPools(check *C) {

if vcd.skipAdminTests {
check.Skip("this test requires system administrator privileges")
}
knownNetworkPoolName := vcd.config.VCD.NsxtProviderVdc.NetworkPool
networkPools, err := vcd.client.GetNetworkPoolSummaries(nil)
check.Assert(err, IsNil)
check.Assert(len(networkPools) > 0, Equals, true)

checkNetworkPoolName := false
foundNetworkPool := false
if knownNetworkPoolName != "" {
checkNetworkPoolName = true
}

for i, nps := range networkPools {
if nps.Name == knownNetworkPoolName {
foundNetworkPool = true
}
networkPoolById, err := vcd.client.GetNetworkPoolById(nps.Id)
check.Assert(err, IsNil)
check.Assert(networkPoolById, NotNil)
check.Assert(networkPoolById.NetworkPool.Id, Equals, nps.Id)
check.Assert(networkPoolById.NetworkPool.Name, Equals, nps.Name)

networkPoolByName, err := vcd.client.GetNetworkPoolByName(nps.Name)
check.Assert(err, IsNil)
check.Assert(networkPoolByName, NotNil)
check.Assert(networkPoolByName.NetworkPool.Id, Equals, nps.Id)
check.Assert(networkPoolByName.NetworkPool.Name, Equals, nps.Name)
if testVerbose {
fmt.Printf("%d, %# v\n", i, pretty.Formatter(networkPoolByName.NetworkPool))
}
}
if checkNetworkPoolName {
check.Assert(foundNetworkPool, Equals, true)
}

}
7 changes: 7 additions & 0 deletions govcd/openapi_endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ var endpointMinApiVersions = map[string]string{
types.OpenApiPathVersion2_0_0 + types.OpenApiEndpointVdcAssignedComputePolicies: "35.0",
types.OpenApiPathVersion2_0_0 + types.OpenApiEndpointVdcComputePolicies: "35.0",
types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointVdcNetworkProfile: "36.0", // VCD 10.3+
types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointVirtualCenters: "36.0",
types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointResourcePools: "36.0",
types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointResourcePoolsBrowseAll: "36.2",
types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointResourcePoolHardware: "36.0",
types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointNetworkPools: "36.0",
types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointNetworkPoolSummaries: "36.0",
types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointStorageProfiles: "33.0",

// Extensions API endpoints. These are not versioned
types.OpenApiEndpointExtensionsUi: "35.0", // VCD 10.2+
Expand Down
Loading

0 comments on commit 91abc52

Please sign in to comment.