diff --git a/.changes/v2.17.0/502-improvements.md b/.changes/v2.17.0/502-improvements.md index 0b5236787..2e80be25c 100644 --- a/.changes/v2.17.0/502-improvements.md +++ b/.changes/v2.17.0/502-improvements.md @@ -1,2 +1,4 @@ -* Updated VDC Compute Policies retrieval methods `AdminVdc.GetAllAssignedVdcComputePolicies` and `Org.GetAllVdcComputePolicies` - from OpenAPI v1.0.0 to v2.0.0, this version supports more filtering options like `isVgpuPolicy`[GH-502] +* Created new VDC Compute Policies CRUD methods using OpenAPI v2.0.0: + `VCDClient.GetVdcComputePolicyV2ById`, `VCDClient.GetAllVdcComputePoliciesV2`, `VCDClient.CreateVdcComputePolicyV2`, + `VdcComputePolicyV2.Update`, `VdcComputePolicyV2.Delete` and `AdminVdc.GetAllAssignedVdcComputePoliciesV2`. + This version supports more filtering options like `isVgpuPolicy` [GH-502], [GH-504] diff --git a/.changes/v2.17.0/504-bug-fixes.md b/.changes/v2.17.0/504-bug-fixes.md new file mode 100644 index 000000000..777821e58 --- /dev/null +++ b/.changes/v2.17.0/504-bug-fixes.md @@ -0,0 +1 @@ +* Changed `VdcComputePolicy.Description` to a non-omitempty pointer, to be able to send null values to VCD to set empty descriptions. [GH-504] diff --git a/.changes/v2.17.0/504-deprecations.md b/.changes/v2.17.0/504-deprecations.md new file mode 100644 index 000000000..0e16d3034 --- /dev/null +++ b/.changes/v2.17.0/504-deprecations.md @@ -0,0 +1,3 @@ +* Deprecated OpenAPI v1.0.0 VDC Compute Policies CRUD methods in favor of v2.0.0 ones: + `Client.GetVdcComputePolicyById`, `Client.GetAllVdcComputePolicies`, `Client.CreateVdcComputePolicy` + `VdcComputePolicy.Update`, `VdcComputePolicy.Delete` and `AdminVdc.GetAllAssignedVdcComputePolicies` [GH-504] diff --git a/.changes/v2.17.0/504-features.md b/.changes/v2.17.0/504-features.md new file mode 100644 index 000000000..8dcfa53ba --- /dev/null +++ b/.changes/v2.17.0/504-features.md @@ -0,0 +1,2 @@ +* Added new methods `VCDClient.GetVmGroupById`, `VCDClient.GetVmGroupByNamedVmGroupIdAndProviderVdcUrn` and `VCDClient.GetVmGroupByNameAndProviderVdcUrn` to retrieve VM Groups. These are useful to create VM Placement Policies [GH-504] +* Added new methods `VCDClient.GetLogicalVmGroupById`, `VCDClient.CreateLogicalVmGroup` and `LogicalVmGroup.Delete` to manage Logical VM Groups. These are useful to create VM Placement Policies [GH-504] diff --git a/govcd/api_vcd_test.go b/govcd/api_vcd_test.go index cd06bcd2e..34609ff58 100644 --- a/govcd/api_vcd_test.go +++ b/govcd/api_vcd_test.go @@ -134,9 +134,10 @@ type TestConfig struct { NetworkPool string `yaml:"network_pool"` } `yaml:"provider_vdc"` NsxtProviderVdc struct { - Name string `yaml:"name"` - StorageProfile string `yaml:"storage_profile"` - NetworkPool string `yaml:"network_pool"` + Name string `yaml:"name"` + StorageProfile string `yaml:"storage_profile"` + NetworkPool string `yaml:"network_pool"` + PlacementPolicyVmGroup string `yaml:"placementPolicyVmGroup,omitempty"` } `yaml:"nsxt_provider_vdc"` Catalog struct { Name string `yaml:"name,omitempty"` @@ -1529,7 +1530,7 @@ func (vcd *TestVCD) removeLeftoverEntities(entity CleanupEntity) { return case "vdcComputePolicy": - policy, err := vcd.client.Client.GetVdcComputePolicyById(entity.Name) + policy, err := vcd.client.GetVdcComputePolicyV2ById(entity.Name) if policy == nil || err != nil { vcd.infoCleanup(notFoundMsg, "vdcComputePolicy", entity.Name) return @@ -1542,6 +1543,20 @@ func (vcd *TestVCD) removeLeftoverEntities(entity CleanupEntity) { } return + case "logicalVmGroup": + logicalVmGroup, err := vcd.client.GetLogicalVmGroupById(entity.Name) + if logicalVmGroup == nil || err != nil { + vcd.infoCleanup(notFoundMsg, "logicalVmGroup", entity.Name) + return + } + err = logicalVmGroup.Delete() + if err == nil { + vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy) + } else { + vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err) + } + return + default: // If we reach this point, we are trying to clean up an entity that // we aren't prepared for yet. diff --git a/govcd/openapi_endpoints.go b/govcd/openapi_endpoints.go index 7c8745e8e..9e0d61292 100644 --- a/govcd/openapi_endpoints.go +++ b/govcd/openapi_endpoints.go @@ -53,6 +53,7 @@ var endpointMinApiVersions = map[string]string{ 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+ + types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointLogicalVmGroups: "35.0", // VCD 10.2+ // 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/provider_vdc.go b/govcd/provider_vdc.go index e191dddba..7f6256143 100644 --- a/govcd/provider_vdc.go +++ b/govcd/provider_vdc.go @@ -88,6 +88,9 @@ func (vcdClient *VCDClient) GetProviderVdcExtendedById(providerVdcId string) (*P // On failure, returns a nil pointer and an error func (vcdClient *VCDClient) GetProviderVdcByName(providerVdcName string) (*ProviderVdc, error) { providerVdc, err := getProviderVdcByName(vcdClient, providerVdcName, false) + if err != nil { + return nil, err + } return providerVdc.(*ProviderVdc), err } @@ -96,6 +99,9 @@ func (vcdClient *VCDClient) GetProviderVdcByName(providerVdcName string) (*Provi // On failure, returns a nil pointer and an error func (vcdClient *VCDClient) GetProviderVdcExtendedByName(providerVdcName string) (*ProviderVdcExtended, error) { providerVdcExtended, err := getProviderVdcByName(vcdClient, providerVdcName, true) + if err != nil { + return nil, err + } return providerVdcExtended.(*ProviderVdcExtended), err } diff --git a/govcd/provider_vdc_test.go b/govcd/provider_vdc_test.go index 2e953368b..4125c3dd4 100644 --- a/govcd/provider_vdc_test.go +++ b/govcd/provider_vdc_test.go @@ -32,12 +32,26 @@ func (vcd *TestVCD) Test_GetProviderVdc(check *C) { // Common asserts for _, providerVdc := range providerVdcs { check.Assert(providerVdc.ProviderVdc.Name, Equals, vcd.config.VCD.NsxtProviderVdc.Name) - check.Assert(providerVdc.ProviderVdc.StorageProfiles.ProviderVdcStorageProfile[0].Name, Equals, vcd.config.VCD.NsxtProviderVdc.StorageProfile) + foundStorageProfile := false + for _, storageProfile := range providerVdc.ProviderVdc.StorageProfiles.ProviderVdcStorageProfile { + if storageProfile.Name == vcd.config.VCD.NsxtProviderVdc.StorageProfile { + foundStorageProfile = true + break + } + } + check.Assert(foundStorageProfile, Equals, true) check.Assert(*providerVdc.ProviderVdc.IsEnabled, Equals, true) check.Assert(providerVdc.ProviderVdc.ComputeCapacity, NotNil) check.Assert(providerVdc.ProviderVdc.Status, Equals, 1) check.Assert(len(providerVdc.ProviderVdc.NetworkPoolReferences.NetworkPoolReference), Equals, 1) - check.Assert(providerVdc.ProviderVdc.NetworkPoolReferences.NetworkPoolReference[0].Name, Equals, vcd.config.VCD.NsxtProviderVdc.NetworkPool) + foundNetworkPool := false + for _, networkPool := range providerVdc.ProviderVdc.NetworkPoolReferences.NetworkPoolReference { + if networkPool.Name == vcd.config.VCD.NsxtProviderVdc.NetworkPool { + foundNetworkPool = true + break + } + } + check.Assert(foundNetworkPool, Equals, true) check.Assert(providerVdc.ProviderVdc.Link, NotNil) } } @@ -62,12 +76,26 @@ func (vcd *TestVCD) Test_GetProviderVdcExtended(check *C) { for _, providerVdcExtended := range providerVdcsExtended { // Basic PVDC asserts check.Assert(providerVdcExtended.VMWProviderVdc.Name, Equals, vcd.config.VCD.NsxtProviderVdc.Name) - check.Assert(providerVdcExtended.VMWProviderVdc.StorageProfiles.ProviderVdcStorageProfile[0].Name, Equals, vcd.config.VCD.NsxtProviderVdc.StorageProfile) + foundStorageProfile := false + for _, storageProfile := range providerVdcExtended.VMWProviderVdc.StorageProfiles.ProviderVdcStorageProfile { + if storageProfile.Name == vcd.config.VCD.NsxtProviderVdc.StorageProfile { + foundStorageProfile = true + break + } + } + check.Assert(foundStorageProfile, Equals, true) check.Assert(*providerVdcExtended.VMWProviderVdc.IsEnabled, Equals, true) check.Assert(providerVdcExtended.VMWProviderVdc.ComputeCapacity, NotNil) check.Assert(providerVdcExtended.VMWProviderVdc.Status, Equals, 1) check.Assert(len(providerVdcExtended.VMWProviderVdc.NetworkPoolReferences.NetworkPoolReference), Equals, 1) - check.Assert(providerVdcExtended.VMWProviderVdc.NetworkPoolReferences.NetworkPoolReference[0].Name, Equals, vcd.config.VCD.NsxtProviderVdc.NetworkPool) + foundNetworkPool := false + for _, networkPool := range providerVdcExtended.VMWProviderVdc.NetworkPoolReferences.NetworkPoolReference { + if networkPool.Name == vcd.config.VCD.NsxtProviderVdc.NetworkPool { + foundNetworkPool = true + break + } + } + check.Assert(foundNetworkPool, Equals, true) check.Assert(providerVdcExtended.VMWProviderVdc.Link, NotNil) // Extended PVDC asserts check.Assert(providerVdcExtended.VMWProviderVdc.ComputeProviderScope, Equals, "vc1") @@ -81,6 +109,31 @@ func (vcd *TestVCD) Test_GetProviderVdcExtended(check *C) { } } +func (vcd *TestVCD) Test_GetNonExistentProviderVdc(check *C) { + if vcd.skipAdminTests { + check.Skip(fmt.Sprintf(TestRequiresSysAdminPrivileges, check.TestName())) + } + + providerVdcExtended, err := vcd.client.GetProviderVdcExtendedByName("non-existent-pvdc") + check.Assert(providerVdcExtended, IsNil) + check.Assert(err, NotNil) + providerVdcExtended, err = vcd.client.GetProviderVdcExtendedById("non-existent-pvdc") + check.Assert(providerVdcExtended, IsNil) + check.Assert(err, NotNil) + providerVdcExtended, err = vcd.client.GetProviderVdcExtendedByHref("non-existent-pvdc") + check.Assert(providerVdcExtended, IsNil) + check.Assert(err, NotNil) + providerVdc, err := vcd.client.GetProviderVdcByName("non-existent-pvdc") + check.Assert(providerVdc, IsNil) + check.Assert(err, NotNil) + providerVdc, err = vcd.client.GetProviderVdcById("non-existent-pvdc") + check.Assert(providerVdc, IsNil) + check.Assert(err, NotNil) + providerVdc, err = vcd.client.GetProviderVdcByHref("non-existent-pvdc") + check.Assert(providerVdc, IsNil) + check.Assert(err, NotNil) +} + func (vcd *TestVCD) Test_GetProviderVdcConvertFromExtendedToNormal(check *C) { if vcd.skipAdminTests { check.Skip(fmt.Sprintf(TestRequiresSysAdminPrivileges, check.TestName())) @@ -91,7 +144,14 @@ func (vcd *TestVCD) Test_GetProviderVdcConvertFromExtendedToNormal(check *C) { providerVdc, err := providerVdcExtended.ToProviderVdc() check.Assert(err, IsNil) check.Assert(providerVdc.ProviderVdc.Name, Equals, vcd.config.VCD.NsxtProviderVdc.Name) - check.Assert(providerVdc.ProviderVdc.StorageProfiles.ProviderVdcStorageProfile[0].Name, Equals, vcd.config.VCD.NsxtProviderVdc.StorageProfile) + foundStorageProfile := false + for _, storageProfile := range providerVdc.ProviderVdc.StorageProfiles.ProviderVdcStorageProfile { + if storageProfile.Name == vcd.config.VCD.NsxtProviderVdc.StorageProfile { + foundStorageProfile = true + break + } + } + check.Assert(foundStorageProfile, Equals, true) check.Assert(*providerVdc.ProviderVdc.IsEnabled, Equals, true) check.Assert(providerVdc.ProviderVdc.Status, Equals, 1) check.Assert(len(providerVdc.ProviderVdc.NetworkPoolReferences.NetworkPoolReference), Equals, 1) diff --git a/govcd/sample_govcd_test_config.yaml b/govcd/sample_govcd_test_config.yaml index ea805c4ef..9fc867472 100644 --- a/govcd/sample_govcd_test_config.yaml +++ b/govcd/sample_govcd_test_config.yaml @@ -58,6 +58,9 @@ vcd: name: nsxTPvdc1 storage_profile: "*" network_pool: "NSX-T Overlay 1" + # A VM Group that needs to exist in the backing vSphere. This VM Group can be used + # to create VM Placement Policies. + placementPolicyVmGroup: testVmGroup nsxt: # NSX-T manager name to be used as defined in VCD manager: nsxManager1 diff --git a/govcd/vapp_test.go b/govcd/vapp_test.go index 818c56ba9..ebd8a8b4d 100644 --- a/govcd/vapp_test.go +++ b/govcd/vapp_test.go @@ -1507,7 +1507,7 @@ func (vcd *TestVCD) Test_AddNewVMWithComputeCapacity(check *C) { client: vcd.org.client, VdcComputePolicy: &types.VdcComputePolicy{ Name: check.TestName() + "_empty", - Description: "Empty policy created by test", + Description: takeStringPointer("Empty policy created by test"), }, } diff --git a/govcd/vdccomputepolicy.go b/govcd/vdccomputepolicy.go index 20c48f4a5..623035b8b 100644 --- a/govcd/vdccomputepolicy.go +++ b/govcd/vdccomputepolicy.go @@ -6,14 +6,13 @@ package govcd import ( "fmt" - "net/http" "net/url" "github.com/vmware/go-vcloud-director/v2/types/v56" - "github.com/vmware/go-vcloud-director/v2/util" ) -// In UI called VM sizing policy. In API VDC compute policy +// VdcComputePolicy defines a VDC Compute Policy, which can be a VM Sizing Policy, a VM Placement Policy or a vGPU Policy. +// Deprecated: Use VdcComputePolicyV2 instead type VdcComputePolicy struct { VdcComputePolicy *types.VdcComputePolicy Href string @@ -21,23 +20,25 @@ type VdcComputePolicy struct { } // GetVdcComputePolicyById retrieves VDC compute policy by given ID +// Deprecated: Use VCDClient.GetVdcComputePolicyV2ById instead func (client *Client) GetVdcComputePolicyById(id string) (*VdcComputePolicy, error) { return getVdcComputePolicyById(client, id) } // GetVdcComputePolicyById retrieves VDC compute policy by given ID -// Deprecated: use client.GetVdcComputePolicyById +// Deprecated: use VCDClient.GetVdcComputePolicyV2ById func (org *AdminOrg) GetVdcComputePolicyById(id string) (*VdcComputePolicy, error) { return getVdcComputePolicyById(org.client, id) } // GetVdcComputePolicyById retrieves VDC compute policy by given ID -// Deprecated: use client.GetVdcComputePolicyById +// Deprecated: use VCDClient.GetVdcComputePolicyV2ById func (org *Org) GetVdcComputePolicyById(id string) (*VdcComputePolicy, error) { return getVdcComputePolicyById(org.client, id) } // getVdcComputePolicyById retrieves VDC compute policy by given ID +// Deprecated: Use getVdcComputePolicyV2ById instead func getVdcComputePolicyById(client *Client, id string) (*VdcComputePolicy, error) { endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointVdcComputePolicies minimumApiVersion, err := client.checkOpenApiEndpointCompatibility(endpoint) @@ -71,28 +72,30 @@ func getVdcComputePolicyById(client *Client, id string) (*VdcComputePolicy, erro // GetAllVdcComputePolicies retrieves all VDC compute policies using OpenAPI endpoint. Query parameters can be supplied to perform additional // filtering +// Deprecated: use VCDClient.GetAllVdcComputePoliciesV2 func (client *Client) GetAllVdcComputePolicies(queryParameters url.Values) ([]*VdcComputePolicy, error) { return getAllVdcComputePolicies(client, queryParameters) } // GetAllVdcComputePolicies retrieves all VDC compute policies using OpenAPI endpoint. Query parameters can be supplied to perform additional // filtering -// Deprecated: use client.GetAllVdcComputePolicies +// Deprecated: use VCDClient.GetAllVdcComputePoliciesV2 func (org *AdminOrg) GetAllVdcComputePolicies(queryParameters url.Values) ([]*VdcComputePolicy, error) { return getAllVdcComputePolicies(org.client, queryParameters) } // GetAllVdcComputePolicies retrieves all VDC compute policies using OpenAPI endpoint. Query parameters can be supplied to perform additional // filtering -// Deprecated: use client.GetAllVdcComputePolicies +// Deprecated: use VCDClient.GetAllVdcComputePoliciesV2 func (org *Org) GetAllVdcComputePolicies(queryParameters url.Values) ([]*VdcComputePolicy, error) { return getAllVdcComputePolicies(org.client, queryParameters) } // getAllVdcComputePolicies retrieves all VDC compute policies using OpenAPI endpoint. Query parameters can be supplied to perform additional // filtering +// Deprecated: use getAllVdcComputePoliciesV2 func getAllVdcComputePolicies(client *Client, queryParameters url.Values) ([]*VdcComputePolicy, error) { - endpoint := types.OpenApiPathVersion2_0_0 + types.OpenApiEndpointVdcComputePolicies + endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointVdcComputePolicies minimumApiVersion, err := client.checkOpenApiEndpointCompatibility(endpoint) if err != nil { return nil, err @@ -123,12 +126,13 @@ func getAllVdcComputePolicies(client *Client, queryParameters url.Values) ([]*Vd } // CreateVdcComputePolicy creates a new VDC Compute Policy using OpenAPI endpoint -// Deprecated: use client.CreateVdcComputePolicy +// Deprecated: use VCDClient.CreateVdcComputePolicyV2 func (org *AdminOrg) CreateVdcComputePolicy(newVdcComputePolicy *types.VdcComputePolicy) (*VdcComputePolicy, error) { return org.client.CreateVdcComputePolicy(newVdcComputePolicy) } // CreateVdcComputePolicy creates a new VDC Compute Policy using OpenAPI endpoint +// Deprecated: use VCDClient.CreateVdcComputePolicyV2 func (client *Client) CreateVdcComputePolicy(newVdcComputePolicy *types.VdcComputePolicy) (*VdcComputePolicy, error) { endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointVdcComputePolicies minimumApiVersion, err := client.checkOpenApiEndpointCompatibility(endpoint) @@ -155,6 +159,7 @@ func (client *Client) CreateVdcComputePolicy(newVdcComputePolicy *types.VdcCompu } // Update existing VDC compute policy +// Deprecated: use VdcComputePolicyV2.Update func (vdcComputePolicy *VdcComputePolicy) Update() (*VdcComputePolicy, error) { endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointVdcComputePolicies minimumApiVersion, err := vdcComputePolicy.client.checkOpenApiEndpointCompatibility(endpoint) @@ -185,6 +190,7 @@ func (vdcComputePolicy *VdcComputePolicy) Update() (*VdcComputePolicy, error) { } // Delete deletes VDC compute policy +// Deprecated: use VdcComputePolicyV2.Delete func (vdcComputePolicy *VdcComputePolicy) Delete() error { endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointVdcComputePolicies minimumApiVersion, err := vdcComputePolicy.client.checkOpenApiEndpointCompatibility(endpoint) @@ -212,8 +218,9 @@ func (vdcComputePolicy *VdcComputePolicy) Delete() error { // GetAllAssignedVdcComputePolicies retrieves all VDC assigned compute policies using OpenAPI endpoint. Query parameters can be supplied to perform additional // filtering +// Deprecated: use AdminVdc.GetAllAssignedVdcComputePoliciesV2 func (vdc *AdminVdc) GetAllAssignedVdcComputePolicies(queryParameters url.Values) ([]*VdcComputePolicy, error) { - endpoint := types.OpenApiPathVersion2_0_0 + types.OpenApiEndpointVdcAssignedComputePolicies + endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointVdcAssignedComputePolicies minimumApiVersion, err := vdc.client.checkOpenApiEndpointCompatibility(endpoint) if err != nil { return nil, err @@ -242,34 +249,3 @@ func (vdc *AdminVdc) GetAllAssignedVdcComputePolicies(queryParameters url.Values return wrappedVdcComputePolicies, nil } - -// SetAssignedComputePolicies assign(set) compute policies. -func (vdc *AdminVdc) SetAssignedComputePolicies(computePolicyReferences types.VdcComputePolicyReferences) (*types.VdcComputePolicyReferences, error) { - util.Logger.Printf("[TRACE] Set Compute Policies started") - - if !vdc.client.IsSysAdmin { - return nil, fmt.Errorf("functionality requires System Administrator privileges") - } - - adminVdcPolicyHREF, err := url.ParseRequestURI(vdc.AdminVdc.HREF) - if err != nil { - return nil, fmt.Errorf("error parsing VDC URL: %s", err) - } - - vdcId, err := GetUuidFromHref(vdc.AdminVdc.HREF, true) - if err != nil { - return nil, fmt.Errorf("unable to get vdc ID from HREF: %s", err) - } - adminVdcPolicyHREF.Path = "/api/admin/vdc/" + vdcId + "/computePolicies" - - returnedVdcComputePolicies := &types.VdcComputePolicyReferences{} - computePolicyReferences.Xmlns = types.XMLNamespaceVCloud - - _, err = vdc.client.ExecuteRequest(adminVdcPolicyHREF.String(), http.MethodPut, - types.MimeVdcComputePolicyReferences, "error setting compute policies for VDC: %s", computePolicyReferences, returnedVdcComputePolicies) - if err != nil { - return nil, err - } - - return returnedVdcComputePolicies, nil -} diff --git a/govcd/vdccomputepolicy_test.go b/govcd/vdccomputepolicy_test.go index 0af02b738..d11dc3af8 100644 --- a/govcd/vdccomputepolicy_test.go +++ b/govcd/vdccomputepolicy_test.go @@ -27,7 +27,7 @@ func (vcd *TestVCD) Test_VdcComputePolicies(check *C) { client: client, VdcComputePolicy: &types.VdcComputePolicy{ Name: check.TestName() + "_empty", - Description: "Empty policy created by test", + Description: takeStringPointer("Empty policy created by test"), }, } @@ -37,13 +37,13 @@ func (vcd *TestVCD) Test_VdcComputePolicies(check *C) { AddToCleanupList(createdPolicy.VdcComputePolicy.ID, "vdcComputePolicy", "", check.TestName()) check.Assert(createdPolicy.VdcComputePolicy.Name, Equals, newComputePolicy.VdcComputePolicy.Name) - check.Assert(createdPolicy.VdcComputePolicy.Description, Equals, newComputePolicy.VdcComputePolicy.Description) + check.Assert(*createdPolicy.VdcComputePolicy.Description, Equals, *newComputePolicy.VdcComputePolicy.Description) newComputePolicy2 := &VdcComputePolicy{ client: client, VdcComputePolicy: &types.VdcComputePolicy{ Name: check.TestName(), - Description: "Not Empty policy created by test", + Description: takeStringPointer("Not Empty policy created by test"), CPUSpeed: takeIntAddress(100), CPUCount: takeIntAddress(2), CoresPerSocket: takeIntAddress(1), @@ -75,7 +75,7 @@ func (vcd *TestVCD) Test_VdcComputePolicies(check *C) { check.Assert(*createdPolicy2.VdcComputePolicy.MemoryShares, Equals, 500) // Step 2 - update - createdPolicy2.VdcComputePolicy.Description = "Updated description" + createdPolicy2.VdcComputePolicy.Description = takeStringPointer("Updated description") updatedPolicy, err := createdPolicy2.Update() check.Assert(err, IsNil) check.Assert(updatedPolicy.VdcComputePolicy, DeepEquals, createdPolicy2.VdcComputePolicy) @@ -143,7 +143,7 @@ func (vcd *TestVCD) Test_SetAssignedComputePolicies(check *C) { client: org.client, VdcComputePolicy: &types.VdcComputePolicy{ Name: check.TestName() + "1", - Description: "Policy created by Test_SetAssignedComputePolicies", + Description: takeStringPointer("Policy created by Test_SetAssignedComputePolicies"), CoresPerSocket: takeIntAddress(1), CPUReservationGuarantee: takeFloatAddress(0.26), CPULimit: takeIntAddress(200), @@ -157,7 +157,7 @@ func (vcd *TestVCD) Test_SetAssignedComputePolicies(check *C) { client: org.client, VdcComputePolicy: &types.VdcComputePolicy{ Name: check.TestName() + "2", - Description: "Policy created by Test_SetAssignedComputePolicies", + Description: takeStringPointer("Policy created by Test_SetAssignedComputePolicies"), CoresPerSocket: takeIntAddress(2), CPUReservationGuarantee: takeFloatAddress(0.52), CPULimit: takeIntAddress(400), diff --git a/govcd/vdccomputepolicy_v2.go b/govcd/vdccomputepolicy_v2.go new file mode 100644 index 000000000..b7685b7d0 --- /dev/null +++ b/govcd/vdccomputepolicy_v2.go @@ -0,0 +1,241 @@ +package govcd + +/* + * Copyright 2022 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. + */ + +import ( + "fmt" + "net/http" + "net/url" + + "github.com/vmware/go-vcloud-director/v2/types/v56" + "github.com/vmware/go-vcloud-director/v2/util" +) + +// VdcComputePolicyV2 defines a VDC Compute Policy, which can be a VM Sizing Policy, a VM Placement Policy or a vGPU Policy. +type VdcComputePolicyV2 struct { + VdcComputePolicyV2 *types.VdcComputePolicyV2 + client *Client +} + +// GetVdcComputePolicyV2ById retrieves VDC Compute Policy (V2) by given ID +func (client *VCDClient) GetVdcComputePolicyV2ById(id string) (*VdcComputePolicyV2, error) { + return getVdcComputePolicyV2ById(client, id) +} + +// getVdcComputePolicyV2ById retrieves VDC Compute Policy (V2) by given ID +func getVdcComputePolicyV2ById(client *VCDClient, id string) (*VdcComputePolicyV2, error) { + endpoint := types.OpenApiPathVersion2_0_0 + types.OpenApiEndpointVdcComputePolicies + minimumApiVersion, err := client.Client.checkOpenApiEndpointCompatibility(endpoint) + if err != nil { + return nil, err + } + + if id == "" { + return nil, fmt.Errorf("empty VDC id") + } + + urlRef, err := client.Client.OpenApiBuildEndpoint(endpoint, id) + + if err != nil { + return nil, err + } + + vdcComputePolicy := &VdcComputePolicyV2{ + VdcComputePolicyV2: &types.VdcComputePolicyV2{}, + client: &client.Client, + } + + err = client.Client.OpenApiGetItem(minimumApiVersion, urlRef, nil, vdcComputePolicy.VdcComputePolicyV2, nil) + if err != nil { + return nil, err + } + + return vdcComputePolicy, nil +} + +// GetAllVdcComputePoliciesV2 retrieves all VDC Compute Policies (V2) using OpenAPI endpoint. Query parameters can be supplied to perform additional +// filtering +func (client *VCDClient) GetAllVdcComputePoliciesV2(queryParameters url.Values) ([]*VdcComputePolicyV2, error) { + return getAllVdcComputePoliciesV2(client, queryParameters) +} + +// getAllVdcComputePolicies retrieves all VDC Compute Policies (V2) using OpenAPI endpoint. Query parameters can be supplied to perform additional +// filtering +func getAllVdcComputePoliciesV2(client *VCDClient, queryParameters url.Values) ([]*VdcComputePolicyV2, error) { + endpoint := types.OpenApiPathVersion2_0_0 + types.OpenApiEndpointVdcComputePolicies + minimumApiVersion, err := client.Client.checkOpenApiEndpointCompatibility(endpoint) + if err != nil { + return nil, err + } + + urlRef, err := client.Client.OpenApiBuildEndpoint(endpoint) + if err != nil { + return nil, err + } + + responses := []*types.VdcComputePolicyV2{{}} + + err = client.Client.OpenApiGetAllItems(minimumApiVersion, urlRef, queryParameters, &responses, nil) + if err != nil { + return nil, err + } + + var wrappedVdcComputePolicies []*VdcComputePolicyV2 + for _, response := range responses { + wrappedVdcComputePolicy := &VdcComputePolicyV2{ + client: &client.Client, + VdcComputePolicyV2: response, + } + wrappedVdcComputePolicies = append(wrappedVdcComputePolicies, wrappedVdcComputePolicy) + } + + return wrappedVdcComputePolicies, nil +} + +// CreateVdcComputePolicyV2 creates a new VDC Compute Policy (V2) using OpenAPI endpoint +func (client *VCDClient) CreateVdcComputePolicyV2(newVdcComputePolicy *types.VdcComputePolicyV2) (*VdcComputePolicyV2, error) { + endpoint := types.OpenApiPathVersion2_0_0 + types.OpenApiEndpointVdcComputePolicies + minimumApiVersion, err := client.Client.checkOpenApiEndpointCompatibility(endpoint) + if err != nil { + return nil, err + } + + urlRef, err := client.Client.OpenApiBuildEndpoint(endpoint) + if err != nil { + return nil, err + } + + returnVdcComputePolicy := &VdcComputePolicyV2{ + VdcComputePolicyV2: &types.VdcComputePolicyV2{}, + client: &client.Client, + } + + err = client.Client.OpenApiPostItem(minimumApiVersion, urlRef, nil, newVdcComputePolicy, returnVdcComputePolicy.VdcComputePolicyV2, nil) + if err != nil { + return nil, fmt.Errorf("error creating VDC Compute Policy: %s", err) + } + + return returnVdcComputePolicy, nil +} + +// Update existing VDC Compute Policy (V2) +func (vdcComputePolicy *VdcComputePolicyV2) Update() (*VdcComputePolicyV2, error) { + endpoint := types.OpenApiPathVersion2_0_0 + types.OpenApiEndpointVdcComputePolicies + minimumApiVersion, err := vdcComputePolicy.client.checkOpenApiEndpointCompatibility(endpoint) + if err != nil { + return nil, err + } + + if vdcComputePolicy.VdcComputePolicyV2.ID == "" { + return nil, fmt.Errorf("cannot update VDC Compute Policy without ID") + } + + urlRef, err := vdcComputePolicy.client.OpenApiBuildEndpoint(endpoint, vdcComputePolicy.VdcComputePolicyV2.ID) + if err != nil { + return nil, err + } + + returnVdcComputePolicy := &VdcComputePolicyV2{ + VdcComputePolicyV2: &types.VdcComputePolicyV2{}, + client: vdcComputePolicy.client, + } + + err = vdcComputePolicy.client.OpenApiPutItem(minimumApiVersion, urlRef, nil, vdcComputePolicy.VdcComputePolicyV2, returnVdcComputePolicy.VdcComputePolicyV2, nil) + if err != nil { + return nil, fmt.Errorf("error updating VDC Compute Policy: %s", err) + } + + return returnVdcComputePolicy, nil +} + +// Delete deletes VDC Compute Policy (V2) +func (vdcComputePolicy *VdcComputePolicyV2) Delete() error { + endpoint := types.OpenApiPathVersion2_0_0 + types.OpenApiEndpointVdcComputePolicies + minimumApiVersion, err := vdcComputePolicy.client.checkOpenApiEndpointCompatibility(endpoint) + if err != nil { + return err + } + + if vdcComputePolicy.VdcComputePolicyV2.ID == "" { + return fmt.Errorf("cannot delete VDC Compute Policy without id") + } + + urlRef, err := vdcComputePolicy.client.OpenApiBuildEndpoint(endpoint, vdcComputePolicy.VdcComputePolicyV2.ID) + if err != nil { + return err + } + + err = vdcComputePolicy.client.OpenApiDeleteItem(minimumApiVersion, urlRef, nil, nil) + + if err != nil { + return fmt.Errorf("error deleting VDC Compute Policy: %s", err) + } + + return nil +} + +// GetAllAssignedVdcComputePoliciesV2 retrieves all VDC assigned Compute Policies (V2) using OpenAPI endpoint. Query parameters can be supplied to perform additional +// filtering +func (vdc *AdminVdc) GetAllAssignedVdcComputePoliciesV2(queryParameters url.Values) ([]*VdcComputePolicyV2, error) { + endpoint := types.OpenApiPathVersion2_0_0 + types.OpenApiEndpointVdcAssignedComputePolicies + minimumApiVersion, err := vdc.client.checkOpenApiEndpointCompatibility(endpoint) + if err != nil { + return nil, err + } + + urlRef, err := vdc.client.OpenApiBuildEndpoint(fmt.Sprintf(endpoint, vdc.AdminVdc.ID)) + if err != nil { + return nil, err + } + + responses := []*types.VdcComputePolicyV2{{}} + + err = vdc.client.OpenApiGetAllItems(minimumApiVersion, urlRef, queryParameters, &responses, nil) + if err != nil { + return nil, err + } + + var wrappedVdcComputePolicies []*VdcComputePolicyV2 + for _, response := range responses { + wrappedVdcComputePolicy := &VdcComputePolicyV2{ + client: vdc.client, + VdcComputePolicyV2: response, + } + wrappedVdcComputePolicies = append(wrappedVdcComputePolicies, wrappedVdcComputePolicy) + } + + return wrappedVdcComputePolicies, nil +} + +// SetAssignedComputePolicies assign(set) Compute Policies to the receiver VDC. +func (vdc *AdminVdc) SetAssignedComputePolicies(computePolicyReferences types.VdcComputePolicyReferences) (*types.VdcComputePolicyReferences, error) { + util.Logger.Printf("[TRACE] Set Compute Policies started") + + if !vdc.client.IsSysAdmin { + return nil, fmt.Errorf("functionality requires System Administrator privileges") + } + + adminVdcPolicyHREF, err := url.ParseRequestURI(vdc.AdminVdc.HREF) + if err != nil { + return nil, fmt.Errorf("error parsing VDC URL: %s", err) + } + + vdcId, err := GetUuidFromHref(vdc.AdminVdc.HREF, true) + if err != nil { + return nil, fmt.Errorf("unable to get vdc ID from HREF: %s", err) + } + adminVdcPolicyHREF.Path = "/api/admin/vdc/" + vdcId + "/computePolicies" + + returnedVdcComputePolicies := &types.VdcComputePolicyReferences{} + computePolicyReferences.Xmlns = types.XMLNamespaceVCloud + + _, err = vdc.client.ExecuteRequest(adminVdcPolicyHREF.String(), http.MethodPut, + types.MimeVdcComputePolicyReferences, "error setting Compute Policies for VDC: %s", computePolicyReferences, returnedVdcComputePolicies) + if err != nil { + return nil, err + } + + return returnedVdcComputePolicies, nil +} diff --git a/govcd/vdccomputepolicy_v2_test.go b/govcd/vdccomputepolicy_v2_test.go new file mode 100644 index 000000000..88d5472b4 --- /dev/null +++ b/govcd/vdccomputepolicy_v2_test.go @@ -0,0 +1,319 @@ +//go:build vdc || functional || openapi || ALL +// +build vdc functional openapi ALL + +/* + * Copyright 2022 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. + */ + +package govcd + +import ( + "fmt" + "net/url" + "strings" + + "github.com/vmware/go-vcloud-director/v2/types/v56" + . "gopkg.in/check.v1" +) + +func (vcd *TestVCD) Test_VdcComputePoliciesV2(check *C) { + if vcd.skipAdminTests { + check.Skip(fmt.Sprintf(TestRequiresSysAdminPrivileges, check.TestName())) + } + + // Step 1 - Create a new VDC Compute Policy + newComputePolicy := &VdcComputePolicyV2{ + client: &vcd.client.Client, + VdcComputePolicyV2: &types.VdcComputePolicyV2{ + VdcComputePolicy: types.VdcComputePolicy{ + Name: check.TestName() + "_empty", + Description: takeStringPointer("Empty policy created by test"), + }, + PolicyType: "VdcVmPolicy", + }, + } + + createdPolicy, err := vcd.client.CreateVdcComputePolicyV2(newComputePolicy.VdcComputePolicyV2) + check.Assert(err, IsNil) + + AddToCleanupList(createdPolicy.VdcComputePolicyV2.ID, "vdcComputePolicy", "", check.TestName()) + + check.Assert(createdPolicy.VdcComputePolicyV2.Name, Equals, newComputePolicy.VdcComputePolicyV2.Name) + check.Assert(*createdPolicy.VdcComputePolicyV2.Description, Equals, *newComputePolicy.VdcComputePolicyV2.Description) + + newComputePolicy2 := &VdcComputePolicyV2{ + client: &vcd.client.Client, + VdcComputePolicyV2: &types.VdcComputePolicyV2{ + VdcComputePolicy: types.VdcComputePolicy{ + Name: check.TestName(), + Description: takeStringPointer("Not Empty policy created by test"), + CPUSpeed: takeIntAddress(100), + CPUCount: takeIntAddress(2), + CoresPerSocket: takeIntAddress(1), + CPUReservationGuarantee: takeFloatAddress(0.26), + CPULimit: takeIntAddress(200), + CPUShares: takeIntAddress(5), + Memory: takeIntAddress(1600), + MemoryReservationGuarantee: takeFloatAddress(0.5), + MemoryLimit: takeIntAddress(1200), + MemoryShares: takeIntAddress(500), + }, + PolicyType: "VdcVmPolicy", + }, + } + + createdPolicy2, err := vcd.client.CreateVdcComputePolicyV2(newComputePolicy2.VdcComputePolicyV2) + check.Assert(err, IsNil) + + AddToCleanupList(createdPolicy2.VdcComputePolicyV2.ID, "vdcComputePolicy", "", check.TestName()) + + check.Assert(createdPolicy2.VdcComputePolicyV2.Name, Equals, newComputePolicy2.VdcComputePolicyV2.Name) + check.Assert(*createdPolicy2.VdcComputePolicyV2.CPUSpeed, Equals, 100) + check.Assert(*createdPolicy2.VdcComputePolicyV2.CPUCount, Equals, 2) + check.Assert(*createdPolicy2.VdcComputePolicyV2.CoresPerSocket, Equals, 1) + check.Assert(*createdPolicy2.VdcComputePolicyV2.CPUReservationGuarantee, Equals, 0.26) + check.Assert(*createdPolicy2.VdcComputePolicyV2.CPULimit, Equals, 200) + check.Assert(*createdPolicy2.VdcComputePolicyV2.CPUShares, Equals, 5) + check.Assert(*createdPolicy2.VdcComputePolicyV2.Memory, Equals, 1600) + check.Assert(*createdPolicy2.VdcComputePolicyV2.MemoryReservationGuarantee, Equals, 0.5) + check.Assert(*createdPolicy2.VdcComputePolicyV2.MemoryLimit, Equals, 1200) + check.Assert(*createdPolicy2.VdcComputePolicyV2.MemoryShares, Equals, 500) + + // Step 2 - Update + createdPolicy2.VdcComputePolicyV2.Description = takeStringPointer("Updated description") + updatedPolicy, err := createdPolicy2.Update() + check.Assert(err, IsNil) + check.Assert(updatedPolicy.VdcComputePolicyV2, DeepEquals, createdPolicy2.VdcComputePolicyV2) + + // Step 3 - Get all VDC compute policies + allExistingPolicies, err := vcd.client.GetAllVdcComputePoliciesV2(nil) + check.Assert(err, IsNil) + check.Assert(allExistingPolicies, NotNil) + + // Step 4 - Get all VDC compute policies using query filters + for _, onePolicy := range allExistingPolicies { + + // Step 3.1 - Retrieve using FIQL filter + queryParams := url.Values{} + queryParams.Add("filter", "id=="+onePolicy.VdcComputePolicyV2.ID) + + expectOnePolicyResultById, err := vcd.client.GetAllVdcComputePoliciesV2(queryParams) + check.Assert(err, IsNil) + check.Assert(len(expectOnePolicyResultById) == 1, Equals, true) + + // Step 2.2 - Retrieve + exactItem, err := vcd.client.GetVdcComputePolicyV2ById(onePolicy.VdcComputePolicyV2.ID) + check.Assert(err, IsNil) + + check.Assert(err, IsNil) + check.Assert(exactItem, NotNil) + + // Step 2.3 - Compare struct retrieved by using filter and the one retrieved by exact ID + check.Assert(onePolicy, DeepEquals, expectOnePolicyResultById[0]) + + } + + // Step 5 - Delete + err = createdPolicy.Delete() + check.Assert(err, IsNil) + // Step 5 - Try to read deleted VDC computed policy should end up with error 'ErrorEntityNotFound' + deletedPolicy, err := vcd.client.GetVdcComputePolicyV2ById(createdPolicy.VdcComputePolicyV2.ID) + check.Assert(ContainsNotFound(err), Equals, true) + check.Assert(deletedPolicy, IsNil) + + err = createdPolicy2.Delete() + check.Assert(err, IsNil) + deletedPolicy2, err := vcd.client.GetVdcComputePolicyV2ById(createdPolicy2.VdcComputePolicyV2.ID) + check.Assert(ContainsNotFound(err), Equals, true) + check.Assert(deletedPolicy2, IsNil) +} + +func (vcd *TestVCD) Test_SetAssignedComputePoliciesV2(check *C) { + if vcd.skipAdminTests { + check.Skip(fmt.Sprintf(TestRequiresSysAdminPrivileges, check.TestName())) + } + + org, err := vcd.client.GetAdminOrgByName(vcd.org.Org.Name) + check.Assert(err, IsNil) + check.Assert(org, NotNil) + + adminVdc, err := org.GetAdminVDCByName(vcd.vdc.Vdc.Name, false) + if adminVdc == nil || err != nil { + vcd.infoCleanup(notFoundMsg, "vdc", vcd.vdc.Vdc.Name) + } + + // Create a new VDC compute policies + newComputePolicy := &VdcComputePolicyV2{ + client: &vcd.client.Client, + VdcComputePolicyV2: &types.VdcComputePolicyV2{ + VdcComputePolicy: types.VdcComputePolicy{ + Name: check.TestName() + "1", + Description: takeStringPointer("Policy created by Test_SetAssignedComputePolicies"), + CoresPerSocket: takeIntAddress(1), + CPUReservationGuarantee: takeFloatAddress(0.26), + CPULimit: takeIntAddress(200), + }, + PolicyType: "VdcVmPolicy", + }, + } + createdPolicy, err := vcd.client.CreateVdcComputePolicyV2(newComputePolicy.VdcComputePolicyV2) + check.Assert(err, IsNil) + AddToCleanupList(createdPolicy.VdcComputePolicyV2.ID, "vdcComputePolicy", "", check.TestName()) + + newComputePolicy2 := &VdcComputePolicyV2{ + client: &vcd.client.Client, + VdcComputePolicyV2: &types.VdcComputePolicyV2{ + VdcComputePolicy: types.VdcComputePolicy{ + Name: check.TestName() + "2", + Description: takeStringPointer("Policy created by Test_SetAssignedComputePolicies"), + CoresPerSocket: takeIntAddress(2), + CPUReservationGuarantee: takeFloatAddress(0.52), + CPULimit: takeIntAddress(400), + }, + PolicyType: "VdcVmPolicy", + }, + } + createdPolicy2, err := vcd.client.CreateVdcComputePolicyV2(newComputePolicy2.VdcComputePolicyV2) + check.Assert(err, IsNil) + AddToCleanupList(createdPolicy2.VdcComputePolicyV2.ID, "vdcComputePolicy", "", check.TestName()) + + // Get default compute policy + allAssignedComputePolicies, err := adminVdc.GetAllAssignedVdcComputePoliciesV2(nil) + check.Assert(err, IsNil) + var defaultPolicyId string + for _, assignedPolicy := range allAssignedComputePolicies { + if assignedPolicy.VdcComputePolicyV2.ID == vcd.vdc.Vdc.DefaultComputePolicy.ID { + defaultPolicyId = assignedPolicy.VdcComputePolicyV2.ID + } + } + + vdcComputePolicyHref, err := org.client.OpenApiBuildEndpoint(types.OpenApiPathVersion2_0_0, types.OpenApiEndpointVdcComputePolicies) + check.Assert(err, IsNil) + + // Assign compute policies to VDC + policyReferences := types.VdcComputePolicyReferences{VdcComputePolicyReference: []*types.Reference{ + {HREF: vdcComputePolicyHref.String() + createdPolicy.VdcComputePolicyV2.ID}, + {HREF: vdcComputePolicyHref.String() + createdPolicy2.VdcComputePolicyV2.ID}, + {HREF: vdcComputePolicyHref.String() + defaultPolicyId}}} + + assignedVdcComputePolicies, err := adminVdc.SetAssignedComputePolicies(policyReferences) + check.Assert(err, IsNil) + check.Assert(strings.SplitAfter(policyReferences.VdcComputePolicyReference[0].HREF, "vdcComputePolicy:")[1], Equals, + strings.SplitAfter(assignedVdcComputePolicies.VdcComputePolicyReference[0].HREF, "vdcComputePolicy:")[1]) + check.Assert(strings.SplitAfter(policyReferences.VdcComputePolicyReference[1].HREF, "vdcComputePolicy:")[1], Equals, + strings.SplitAfter(assignedVdcComputePolicies.VdcComputePolicyReference[1].HREF, "vdcComputePolicy:")[1]) + + // Cleanup assigned compute policies + policyReferences = types.VdcComputePolicyReferences{VdcComputePolicyReference: []*types.Reference{ + {HREF: vdcComputePolicyHref.String() + defaultPolicyId}}} + + _, err = adminVdc.SetAssignedComputePolicies(policyReferences) + check.Assert(err, IsNil) + + err = createdPolicy.Delete() + check.Assert(err, IsNil) + err = createdPolicy2.Delete() + check.Assert(err, IsNil) +} + +// Test_VdcVmPlacementPoliciesV2 is similar to Test_VdcComputePoliciesV2 but focused on VM Placement Policies +func (vcd *TestVCD) Test_VdcVmPlacementPoliciesV2(check *C) { + if vcd.skipAdminTests { + check.Skip(fmt.Sprintf(TestRequiresSysAdminPrivileges, check.TestName())) + } + + if vcd.config.VCD.NsxtProviderVdc.PlacementPolicyVmGroup == "" { + check.Skip("The configuration entry vcd.nsxt_provider_vdc.placementPolicyVmGroup is needed") + } + + // We need the Provider VDC URN + pVdc, err := vcd.client.GetProviderVdcByName(vcd.config.VCD.NsxtProviderVdc.Name) + check.Assert(err, IsNil) + + // We also need the VM Group to create a VM Placement Policy + vmGroup, err := vcd.client.GetVmGroupByNameAndProviderVdcUrn(vcd.config.VCD.NsxtProviderVdc.PlacementPolicyVmGroup, pVdc.ProviderVdc.ID) + check.Assert(err, IsNil) + check.Assert(vmGroup.VmGroup.Name, Equals, vcd.config.VCD.NsxtProviderVdc.PlacementPolicyVmGroup) + + // We'll also use a Logical VM Group to create the VM Placement Policy + logicalVmGroup, err := vcd.client.CreateLogicalVmGroup(types.LogicalVmGroup{ + Name: check.TestName(), + NamedVmGroupReferences: types.OpenApiReferences{ + types.OpenApiReference{ + ID: fmt.Sprintf("%s:%s", vmGroupUrnPrefix, vmGroup.VmGroup.NamedVmGroupId), + Name: vmGroup.VmGroup.Name}, + }, + PvdcID: pVdc.ProviderVdc.ID, + }) + check.Assert(err, IsNil) + AddToCleanupList(logicalVmGroup.LogicalVmGroup.ID, "logicalVmGroup", "", check.TestName()) + + // Create a new VDC Compute Policy (VM Placement Policy) + newComputePolicy := &VdcComputePolicyV2{ + client: &vcd.client.Client, + VdcComputePolicyV2: &types.VdcComputePolicyV2{ + VdcComputePolicy: types.VdcComputePolicy{ + Name: check.TestName() + "_empty", + Description: takeStringPointer("VM Placement Policy created by " + check.TestName()), + }, + PolicyType: "VdcVmPolicy", + PvdcNamedVmGroupsMap: []types.PvdcNamedVmGroupsMap{ + { + NamedVmGroups: []types.OpenApiReferences{ + { + types.OpenApiReference{ + Name: vmGroup.VmGroup.Name, + ID: fmt.Sprintf("%s:%s", vmGroupUrnPrefix, vmGroup.VmGroup.NamedVmGroupId), + }, + }, + }, + Pvdc: types.OpenApiReference{ + Name: pVdc.ProviderVdc.Name, + ID: pVdc.ProviderVdc.ID, + }, + }, + }, + PvdcLogicalVmGroupsMap: []types.PvdcLogicalVmGroupsMap{ + { + LogicalVmGroups: types.OpenApiReferences{ + types.OpenApiReference{ + Name: logicalVmGroup.LogicalVmGroup.Name, + ID: logicalVmGroup.LogicalVmGroup.ID, + }, + }, + Pvdc: types.OpenApiReference{ + Name: pVdc.ProviderVdc.Name, + ID: pVdc.ProviderVdc.ID, + }, + }, + }, + }, + } + + createdPolicy, err := vcd.client.CreateVdcComputePolicyV2(newComputePolicy.VdcComputePolicyV2) + check.Assert(err, IsNil) + + AddToCleanupList(createdPolicy.VdcComputePolicyV2.ID, "vdcComputePolicy", "", check.TestName()) + + check.Assert(createdPolicy.VdcComputePolicyV2.Name, Equals, newComputePolicy.VdcComputePolicyV2.Name) + check.Assert(*createdPolicy.VdcComputePolicyV2.Description, Equals, *newComputePolicy.VdcComputePolicyV2.Description) + check.Assert(createdPolicy.VdcComputePolicyV2.PvdcLogicalVmGroupsMap, DeepEquals, newComputePolicy.VdcComputePolicyV2.PvdcLogicalVmGroupsMap) + check.Assert(createdPolicy.VdcComputePolicyV2.PvdcNamedVmGroupsMap, DeepEquals, newComputePolicy.VdcComputePolicyV2.PvdcNamedVmGroupsMap) + + // Update the VM Placement Policy + createdPolicy.VdcComputePolicyV2.Description = takeStringPointer("Updated description") + updatedPolicy, err := createdPolicy.Update() + check.Assert(err, IsNil) + check.Assert(updatedPolicy.VdcComputePolicyV2, DeepEquals, createdPolicy.VdcComputePolicyV2) + + // Delete the VM Placement Policy and check it doesn't exist anymore + err = createdPolicy.Delete() + check.Assert(err, IsNil) + deletedPolicy, err := vcd.client.GetVdcComputePolicyV2ById(createdPolicy.VdcComputePolicyV2.ID) + check.Assert(ContainsNotFound(err), Equals, true) + check.Assert(deletedPolicy, IsNil) + + // Clean up + err = logicalVmGroup.Delete() + check.Assert(err, IsNil) +} diff --git a/govcd/vm_groups.go b/govcd/vm_groups.go new file mode 100644 index 000000000..76bdf6dac --- /dev/null +++ b/govcd/vm_groups.go @@ -0,0 +1,185 @@ +package govcd + +import ( + "fmt" + "github.com/vmware/go-vcloud-director/v2/types/v56" + "net/url" +) + +// LogicalVmGroup is used to create VM Placement Policies. +type LogicalVmGroup struct { + LogicalVmGroup *types.LogicalVmGroup + client *Client +} + +// VmGroup is used to create VM Placement Policies. +type VmGroup struct { + VmGroup *types.QueryResultVmGroupsRecordType + client *Client +} + +// This constant is useful when managing Logical VM Groups by referencing VM Groups, as these are +// XML based and don't deal with IDs with full URNs, while Logical VM Groups are OpenAPI based and they do. +const vmGroupUrnPrefix = "urn:vcloud:namedVmGroup" + +// GetVmGroupById finds a VM Group by its ID. +// On success, returns a pointer to the VmGroup structure and a nil error +// On failure, returns a nil pointer and an error +func (vcdClient *VCDClient) GetVmGroupById(id string) (*VmGroup, error) { + return getVmGroupWithFilter(vcdClient, map[string]string{"vmGroupId": extractUuid(id)}) +} + +// GetVmGroupByNamedVmGroupIdAndProviderVdcUrn finds a VM Group by its Named VM Group ID and Provider VDC URN. +// On success, returns a pointer to the VmGroup structure and a nil error +// On failure, returns a nil pointer and an error +func (vcdClient *VCDClient) GetVmGroupByNamedVmGroupIdAndProviderVdcUrn(namedVmGroupId, pvdcUrn string) (*VmGroup, error) { + resourcePool, err := getResourcePool(vcdClient, pvdcUrn) + if err != nil { + return nil, fmt.Errorf("could not get VM Group: %s", err) + } + return getVmGroupWithFilter(vcdClient, map[string]string{"namedVmGroupId": extractUuid(namedVmGroupId), "clusterMoref": resourcePool.ClusterMoref, "vcId": extractUuid(resourcePool.VcenterHREF)}) +} + +// GetVmGroupByNameAndProviderVdcUrn finds a VM Group by its name and associated Provider VDC URN. +// On success, returns a pointer to the VmGroup structure and a nil error +// On failure, returns a nil pointer and an error +func (vcdClient *VCDClient) GetVmGroupByNameAndProviderVdcUrn(name, pvdcUrn string) (*VmGroup, error) { + resourcePool, err := getResourcePool(vcdClient, pvdcUrn) + if err != nil { + return nil, fmt.Errorf("could not get VM Group: %s", err) + } + return getVmGroupWithFilter(vcdClient, map[string]string{"vmGroupName": name, "clusterMoref": resourcePool.ClusterMoref, "vcId": extractUuid(resourcePool.VcenterHREF)}) +} + +// GetLogicalVmGroupById finds a Logical VM Group by its URN. +// On success, returns a pointer to the LogicalVmGroup structure and a nil error +// On failure, returns a nil pointer and an error +func (vcdClient *VCDClient) GetLogicalVmGroupById(logicalVmGroupId string) (*LogicalVmGroup, error) { + endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointLogicalVmGroups + + apiVersion, err := vcdClient.Client.getOpenApiHighestElevatedVersion(endpoint) + if err != nil { + return nil, err + } + + if logicalVmGroupId == "" { + return nil, fmt.Errorf("empty Logical VM Group id") + } + + urlRef, err := vcdClient.Client.OpenApiBuildEndpoint(endpoint, logicalVmGroupId) + if err != nil { + return nil, err + } + + result := &LogicalVmGroup{ + LogicalVmGroup: &types.LogicalVmGroup{}, + client: &vcdClient.Client, + } + + err = vcdClient.Client.OpenApiGetItem(apiVersion, urlRef, nil, result.LogicalVmGroup, nil) + if err != nil { + return nil, fmt.Errorf("error getting Logical VM Group: %s", err) + } + + return result, nil +} + +// CreateLogicalVmGroup creates a new Logical VM Group in VCD +func (vcdClient *VCDClient) CreateLogicalVmGroup(logicalVmGroup types.LogicalVmGroup) (*LogicalVmGroup, error) { + endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointLogicalVmGroups + + apiVersion, err := vcdClient.Client.getOpenApiHighestElevatedVersion(endpoint) + if err != nil { + return nil, err + } + + urlRef, err := vcdClient.Client.OpenApiBuildEndpoint(endpoint) + if err != nil { + return nil, err + } + + result := &LogicalVmGroup{ + LogicalVmGroup: &types.LogicalVmGroup{}, + client: &vcdClient.Client, + } + + err = vcdClient.Client.OpenApiPostItem(apiVersion, urlRef, nil, logicalVmGroup, result.LogicalVmGroup, nil) + if err != nil { + return nil, fmt.Errorf("error creating the Logical VM Group: %s", err) + } + + return result, nil +} + +// Delete deletes the receiver Logical VM Group +func (logicalVmGroup *LogicalVmGroup) Delete() error { + if logicalVmGroup.LogicalVmGroup.ID == "" { + return fmt.Errorf("cannot delete Logical VM Group without id") + } + + endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointLogicalVmGroups + + apiVersion, err := logicalVmGroup.client.getOpenApiHighestElevatedVersion(endpoint) + if err != nil { + return err + } + + urlRef, err := logicalVmGroup.client.OpenApiBuildEndpoint(endpoint, logicalVmGroup.LogicalVmGroup.ID) + if err != nil { + return err + } + + err = logicalVmGroup.client.OpenApiDeleteItem(apiVersion, urlRef, nil, nil) + if err != nil { + return fmt.Errorf("error deleting the Logical VM Group: %s", err) + } + return nil +} + +// getVmGroupWithFilter finds a VM Group by specifying a filter=(filterKey==filterValue). +// On success, returns a pointer to the VmGroup structure and a nil error +// On failure, returns a nil pointer and an error +func getVmGroupWithFilter(vcdClient *VCDClient, filter map[string]string) (*VmGroup, error) { + filterEncoded := "" + for k, v := range filter { + filterEncoded += fmt.Sprintf("%s==%s;", url.QueryEscape(k), url.QueryEscape(v)) + } + foundVmGroups, err := vcdClient.QueryWithNotEncodedParams(nil, map[string]string{ + "type": "vmGroups", + "filter": filterEncoded[:len(filterEncoded)-1], // Removes the trailing ';' + "filterEncoded": "true", + }) + if err != nil { + return nil, err + } + if len(foundVmGroups.Results.VmGroupsRecord) == 0 { + return nil, ErrorEntityNotFound + } + if len(foundVmGroups.Results.VmGroupsRecord) > 1 { + return nil, fmt.Errorf("more than one VM Group found with the filter: %v", filter) + } + vmGroup := &VmGroup{ + VmGroup: foundVmGroups.Results.VmGroupsRecord[0], + client: &vcdClient.Client, + } + return vmGroup, nil +} + +// getResourcePool returns the Resource Pool that can unequivocally identify a VM Group +func getResourcePool(vcdClient *VCDClient, pvdcUrn string) (*types.QueryResultResourcePoolRecordType, error) { + foundResourcePools, err := vcdClient.QueryWithNotEncodedParams(nil, map[string]string{ + "type": "resourcePool", + "filter": fmt.Sprintf("providerVdc==%s", url.QueryEscape(pvdcUrn)), + "filterEncoded": "true", + }) + if err != nil { + return nil, fmt.Errorf("could not get the Resource pool: %s", err) + } + if len(foundResourcePools.Results.ResourcePoolRecord) == 0 { + return nil, ErrorEntityNotFound + } + if len(foundResourcePools.Results.ResourcePoolRecord) > 1 { + return nil, fmt.Errorf("more than one Resource Pool found for the pVDC: %s", pvdcUrn) + } + return foundResourcePools.Results.ResourcePoolRecord[0], nil +} diff --git a/govcd/vm_groups_test.go b/govcd/vm_groups_test.go new file mode 100644 index 000000000..34fd33bc0 --- /dev/null +++ b/govcd/vm_groups_test.go @@ -0,0 +1,63 @@ +//go:build functional || openapi || ALL +// +build 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" +) + +// This test checks the correct behaviour of the read, create and delete operations for VM Groups and Logical VM Groups. +func (vcd *TestVCD) Test_VmGroupsCRUD(check *C) { + if vcd.skipAdminTests { + check.Skip(fmt.Sprintf(TestRequiresSysAdminPrivileges, check.TestName())) + } + + if vcd.config.VCD.NsxtProviderVdc.PlacementPolicyVmGroup == "" { + check.Skip(fmt.Sprintf("%s test requires vcd.nsxt_provider_vdc.placementPolicyVmGroup configuration", check.TestName())) + } + if vcd.config.VCD.NsxtProviderVdc.Name == "" { + check.Skip(fmt.Sprintf("%s test requires vcd.nsxt_provider_vdc configuration", check.TestName())) + } + + // We need the Provider VDC URN + pVdc, err := vcd.client.GetProviderVdcByName(vcd.config.VCD.NsxtProviderVdc.Name) + check.Assert(err, IsNil) + + vmGroup, err := vcd.client.GetVmGroupByNameAndProviderVdcUrn(vcd.config.VCD.NsxtProviderVdc.PlacementPolicyVmGroup, pVdc.ProviderVdc.ID) + check.Assert(err, IsNil) + check.Assert(vmGroup.VmGroup.Name, Equals, vcd.config.VCD.NsxtProviderVdc.PlacementPolicyVmGroup) + + vmGroup2, err := vcd.client.GetVmGroupById(vmGroup.VmGroup.ID) + check.Assert(err, IsNil) + check.Assert(vmGroup2, DeepEquals, vmGroup) + + vmGroup3, err := vcd.client.GetVmGroupByNamedVmGroupIdAndProviderVdcUrn(vmGroup2.VmGroup.NamedVmGroupId, pVdc.ProviderVdc.ID) + check.Assert(err, IsNil) + check.Assert(vmGroup3, DeepEquals, vmGroup2) + + logicalVmGroup, err := vcd.client.CreateLogicalVmGroup(types.LogicalVmGroup{ + Name: check.TestName(), + NamedVmGroupReferences: types.OpenApiReferences{ + types.OpenApiReference{ + ID: fmt.Sprintf("%s:%s", vmGroupUrnPrefix, vmGroup.VmGroup.NamedVmGroupId), + Name: vmGroup.VmGroup.Name}, + }, + PvdcID: pVdc.ProviderVdc.ID, + }) + check.Assert(err, IsNil) + AddToCleanupList(logicalVmGroup.LogicalVmGroup.ID, "logicalVmGroup", "", check.TestName()) + + retrievedLogicalVmGroup, err := vcd.client.GetLogicalVmGroupById(logicalVmGroup.LogicalVmGroup.ID) + check.Assert(err, IsNil) + check.Assert(retrievedLogicalVmGroup.LogicalVmGroup, DeepEquals, logicalVmGroup.LogicalVmGroup) + + err = logicalVmGroup.Delete() + check.Assert(err, IsNil) +} diff --git a/govcd/vm_test.go b/govcd/vm_test.go index c55efb194..2efeeeaa4 100644 --- a/govcd/vm_test.go +++ b/govcd/vm_test.go @@ -1537,7 +1537,7 @@ func (vcd *TestVCD) Test_AddNewEmptyVMWithVmComputePolicyAndUpdate(check *C) { client: vcd.org.client, VdcComputePolicy: &types.VdcComputePolicy{ Name: check.TestName() + "_empty", - Description: "Empty policy created by test", + Description: takeStringPointer("Empty policy created by test"), }, } @@ -1545,7 +1545,7 @@ func (vcd *TestVCD) Test_AddNewEmptyVMWithVmComputePolicyAndUpdate(check *C) { client: vcd.org.client, VdcComputePolicy: &types.VdcComputePolicy{ Name: check.TestName() + "_memory", - Description: "Empty policy created by test 2", + Description: takeStringPointer("Empty policy created by test 2"), Memory: takeIntAddress(2048), }, } diff --git a/types/v56/constants.go b/types/v56/constants.go index 67031b7ac..ddc4699ef 100644 --- a/types/v56/constants.go +++ b/types/v56/constants.go @@ -374,6 +374,7 @@ const ( OpenApiEndpointVdcGroupsDfwPolicies = "vdcGroups/%s/dfwPolicies" OpenApiEndpointVdcGroupsDfwDefaultPolicies = "vdcGroups/%s/dfwPolicies/default" OpenApiEndpointVdcGroupsDfwRules = "vdcGroups/%s/dfwPolicies/%s/rules" + OpenApiEndpointLogicalVmGroups = "logicalVmGroups/" OpenApiEndpointNetworkContextProfiles = "networkContextProfiles" OpenApiEndpointSecurityTags = "securityTags" OpenApiEndpointNsxtRouteAdvertisement = "edgeGateways/%s/routing/advertisement" diff --git a/types/v56/openapi.go b/types/v56/openapi.go index bbd6c30c8..2ac05c705 100644 --- a/types/v56/openapi.go +++ b/types/v56/openapi.go @@ -155,10 +155,11 @@ type NetworkProvider struct { ID string `json:"id"` } -// VdcComputePolicy is represented as VM sizing policy in UI +// VdcComputePolicy contains VDC specific configuration for workloads. (version 1.0.0) +// Deprecated: Use VdcComputePolicyV2 instead (version 2.0.0) type VdcComputePolicy struct { ID string `json:"id,omitempty"` - Description string `json:"description,omitempty"` + Description *string `json:"description"` // It's a not-omitempty pointer to be able to send "null" values for empty descriptions. Name string `json:"name"` CPUSpeed *int `json:"cpuSpeed,omitempty"` Memory *int `json:"memory,omitempty"` @@ -175,26 +176,38 @@ type VdcComputePolicy struct { AdditionalProp2 string `json:"additionalProp2,omitempty"` AdditionalProp3 string `json:"additionalProp3,omitempty"` } `json:"extraConfigs,omitempty"` - PvdcComputePolicyRef *struct { - Name string `json:"name,omitempty"` - ID string `json:"id,omitempty"` - } `json:"pvdcComputePolicyRef,omitempty"` - PvdcComputePolicy *struct { - Name string `json:"name,omitempty"` - ID string `json:"id,omitempty"` - } `json:"pvdcComputePolicy,omitempty"` - CompatibleVdcTypes []string `json:"compatibleVdcTypes,omitempty"` - IsSizingOnly bool `json:"isSizingOnly,omitempty"` - PvdcID string `json:"pvdcId,omitempty"` - NamedVMGroups [][]struct { - Name string `json:"name,omitempty"` - ID string `json:"id,omitempty"` - } `json:"namedVmGroups,omitempty"` - LogicalVMGroupReferences []struct { - Name string `json:"name,omitempty"` - ID string `json:"id,omitempty"` - } `json:"logicalVmGroupReferences,omitempty"` - IsAutoGenerated bool `json:"isAutoGenerated,omitempty"` + PvdcComputePolicyRef *OpenApiReference `json:"pvdcComputePolicyRef,omitempty"` + PvdcComputePolicy *OpenApiReference `json:"pvdcComputePolicy,omitempty"` + CompatibleVdcTypes []string `json:"compatibleVdcTypes,omitempty"` + IsSizingOnly bool `json:"isSizingOnly,omitempty"` + PvdcID string `json:"pvdcId,omitempty"` + NamedVMGroups []OpenApiReferences `json:"namedVmGroups,omitempty"` + LogicalVMGroupReferences OpenApiReferences `json:"logicalVmGroupReferences,omitempty"` + IsAutoGenerated bool `json:"isAutoGenerated,omitempty"` +} + +// VdcComputePolicyV2 contains VDC specific configuration for workloads (version 2.0.0) +// https://developer.vmware.com/apis/vmware-cloud-director/latest/data-structures/VdcComputePolicy2/ +type VdcComputePolicyV2 struct { + VdcComputePolicy + PolicyType string `json:"policyType"` // Required. Can be "VdcVmPolicy" or "VdcKubernetesPolicy" + IsVgpuPolicy bool `json:"isVgpuPolicy,omitempty"` + PvdcNamedVmGroupsMap []PvdcNamedVmGroupsMap `json:"pvdcNamedVmGroupsMap,omitempty"` + PvdcLogicalVmGroupsMap []PvdcLogicalVmGroupsMap `json:"pvdcLogicalVmGroupsMap,omitempty"` +} + +// PvdcNamedVmGroupsMap is a combination of a reference to a Provider VDC and a list of references to Named VM Groups. +// This is used for VM Placement Policies (see VdcComputePolicyV2) +type PvdcNamedVmGroupsMap struct { + NamedVmGroups []OpenApiReferences `json:"namedVmGroups,omitempty"` + Pvdc OpenApiReference `json:"pvdc,omitempty"` +} + +// PvdcLogicalVmGroupsMap is a combination of a reference to a Provider VDC and a list of references to Logical VM Groups. +// This is used for VM Placement Policies (see VdcComputePolicyV2) +type PvdcLogicalVmGroupsMap struct { + LogicalVmGroups OpenApiReferences `json:"logicalVmGroups,omitempty"` + Pvdc OpenApiReference `json:"pvdc,omitempty"` } // OpenApiReference is a generic reference type commonly used throughout OpenAPI endpoints @@ -404,3 +417,12 @@ type ProbeResult struct { CertificateChain string `json:"certificateChain,omitempty"` // The SSL certificate chain presented by the server if a secure connection was made. AdditionalCAIssuers []string `json:"additionalCAIssuers,omitempty"` // URLs supplied by Certificate Authorities to retrieve signing certificates, when those certificates are not included in the chain. } + +// LogicalVmGroup is used to create VM Placement Policies in VCD. +type LogicalVmGroup struct { + Name string `json:"name,omitempty"` // Display name + Description string `json:"description,omitempty"` + ID string `json:"id,omitempty"` // UUID for LogicalVmGroup. This is immutable + NamedVmGroupReferences OpenApiReferences `json:"namedVmGroupReferences,omitempty"` // List of named VM Groups associated with LogicalVmGroup. + PvdcID string `json:"pvdcId,omitempty"` // URN for Provider VDC +} diff --git a/types/v56/types.go b/types/v56/types.go index 4178fcf1e..91c5fed37 100644 --- a/types/v56/types.go +++ b/types/v56/types.go @@ -2312,6 +2312,35 @@ type QueryResultRecordsType struct { NsxtManagerRecord []*QueryResultNsxtManagerRecordType `xml:"NsxTManagerRecord"` // A record representing NSX-T manager OrgVdcRecord []*QueryResultOrgVdcRecordType `xml:"OrgVdcRecord"` // A record representing Org VDC OrgVdcAdminRecord []*QueryResultOrgVdcRecordType `xml:"AdminVdcRecord"` // A record representing Org VDC + ResourcePoolRecord []*QueryResultResourcePoolRecordType `xml:"ResourcePoolRecord"` // A record representing a Resource Pool + VmGroupsRecord []*QueryResultVmGroupsRecordType `xml:"VmGroupsRecord"` // A record representing a VM Group +} + +// QueryResultVmGroupsRecordType represent a VM Groups record +type QueryResultVmGroupsRecordType struct { + HREF string `xml:"href,attr,omitempty"` + ID string `xml:"vmGroupId,attr,omitempty"` + Name string `xml:"vmGroupName,attr,omitempty"` + ClusterMoref string `xml:"clusterMoref,attr,omitempty"` + ClusterName string `xml:"clusterName,attr,omitempty"` + VcenterId string `xml:"vcId,attr,omitempty"` + NamedVmGroupId string `xml:"namedVmGroupId,attr,omitempty"` +} + +// QueryResultResourcePoolRecordType represent a Resource Pool record +type QueryResultResourcePoolRecordType struct { + HREF string `xml:"href,attr,omitempty"` + Name string `xml:"name,attr,omitempty"` + Moref string `xml:"moref,attr,omitempty"` + IsDeleted bool `xml:"isDeleted,attr,omitempty"` + VcenterHREF string `xml:"vc,attr,omitempty"` + VcenterName string `xml:"vcName,attr,omitempty"` + ProviderVdcHREF string `xml:"providerVdc,attr,omitempty"` + ProviderName string `xml:"providerName,attr,omitempty"` + IsEnabled bool `xml:"isEnabled,attr,omitempty"` + IsPrimary bool `xml:"isPrimary,attr,omitempty"` + ClusterMoref string `xml:"clusterMoref,attr,omitempty"` + IsKubernetesEnabled bool `xml:"isKubernetesEnabled,attr,omitempty"` } // QueryResultOrgVdcRecordType represents an Org VDC record