From a7edcad781dec901e273b6f8e9d51fa91256cd25 Mon Sep 17 00:00:00 2001 From: Giuseppe Maxia Date: Fri, 6 Aug 2021 09:29:41 +0200 Subject: [PATCH] Update vdc storage profiles (#393) * Add structure needed to update storage profiles * Add methods to add/remove VDC storage profiles * Replace methods used to search storage profiles * Deprecate vdc.GetDefaultStorageProfileReference * Add tests for storage profile handling * Add CHANGELOG items Signed-off-by: Giuseppe Maxia --- .changes/v2.13.0/393-deprecations.md | 4 + .changes/v2.13.0/393-features.md | 11 ++ govcd/adminvdc.go | 168 ++++++++++++++++++++ govcd/adminvdc_nsxt_test.go | 7 +- govcd/adminvdc_test.go | 9 +- govcd/catalog.go | 2 +- govcd/catalogitem_test.go | 36 +++++ govcd/common_test.go | 19 +-- govcd/org_test.go | 226 ++++++++++++++++++++++++--- govcd/system.go | 92 ++++++++++- govcd/system_test.go | 4 +- govcd/vdc.go | 2 + types/v56/constants.go | 2 + types/v56/types.go | 10 ++ 14 files changed, 543 insertions(+), 49 deletions(-) create mode 100644 .changes/v2.13.0/393-deprecations.md create mode 100644 .changes/v2.13.0/393-features.md diff --git a/.changes/v2.13.0/393-deprecations.md b/.changes/v2.13.0/393-deprecations.md new file mode 100644 index 000000000..9e3325129 --- /dev/null +++ b/.changes/v2.13.0/393-deprecations.md @@ -0,0 +1,4 @@ +* Deprecated `GetStorageProfileByHref` in favor of either `client.GetStorageProfileByHref` or `vcdClient.GetStorageProfileByHref` [GH-393] +* Deprecated `QueryProviderVdcStorageProfileByName` in favor of `VCDClient.QueryProviderVdcStorageProfileByName` [GH-393] +* Deprecated `VCDClient.QueryProviderVdcStorageProfiles` in favor of either `client.QueryProviderVdcStorageProfiles` or `client.QueryAllProviderVdcStorageProfiles` [GH-393] +* Deprecated `Vdc.GetDefaultStorageProfileReference` in favor of `adminVdc.GetDefaultStorageProfileReference` [GH-393] diff --git a/.changes/v2.13.0/393-features.md b/.changes/v2.13.0/393-features.md new file mode 100644 index 000000000..2ef8cd77c --- /dev/null +++ b/.changes/v2.13.0/393-features.md @@ -0,0 +1,11 @@ +* Added method `AdminVdc.AddStorageProfile` [GH-393] +* Added method `AdminVdc.AddStorageProfileWait` [GH-393] +* Added method `AdminVdc.RemoveStorageProfile` [GH-393] +* Added method `AdminVdc.RemoveStorageProfileWait` [GH-393] +* Added method `AdminVdc.SetDefaultStorageProfile` [GH-393] +* Added method `AdminVdc.GetDefaultStorageProfileReference` [GH-393] +* Added method `VCDClient.GetStorageProfileByHref` [GH-393] +* Added method `Client.GetStorageProfileByHref` [GH-393] +* Added method `VCDClient.QueryProviderVdcStorageProfileByName` [GH-393] +* Added method `Client.QueryAllProviderVdcStorageProfiles` [GH-393] +* Added method `Client.QueryProviderVdcStorageProfiles` [GH-393] diff --git a/govcd/adminvdc.go b/govcd/adminvdc.go index 20170d0df..5f58785bb 100644 --- a/govcd/adminvdc.go +++ b/govcd/adminvdc.go @@ -424,3 +424,171 @@ func (vdc *AdminVdc) UpdateStorageProfile(storageProfileId string, storageProfil return updateAdminVdcStorageProfile, err } + +// AddStorageProfile adds a storage profile to a VDC +func (vdc *AdminVdc) AddStorageProfile(storageProfile *types.VdcStorageProfileConfiguration, description string) (Task, error) { + if vdc.client.VCDHREF.String() == "" { + return Task{}, fmt.Errorf("cannot add VDC storage profile, VCD HREF is unset") + } + + href := vdc.AdminVdc.HREF + "/vdcStorageProfiles" + + var updateStorageProfile = types.UpdateVdcStorageProfiles{ + Xmlns: types.XMLNamespaceVCloud, + Name: storageProfile.ProviderVdcStorageProfile.Name, + Description: description, + AddStorageProfile: storageProfile, + RemoveStorageProfile: nil, + } + + task, err := vdc.client.ExecuteTaskRequest(href, http.MethodPost, + types.MimeUpdateVdcStorageProfiles, "error adding VDC storage profile: %s", &updateStorageProfile) + if err != nil { + return Task{}, fmt.Errorf("cannot add VDC storage profile, error: %s", err) + } + + return task, nil +} + +// AddStorageProfileWait adds a storage profile to a VDC and return a refreshed VDC +func (vdc *AdminVdc) AddStorageProfileWait(storageProfile *types.VdcStorageProfileConfiguration, description string) error { + task, err := vdc.AddStorageProfile(storageProfile, description) + if err != nil { + return err + } + err = task.WaitTaskCompletion() + if err != nil { + return err + } + return vdc.Refresh() +} + +// RemoveStorageProfile remove a storage profile from a VDC +func (vdc *AdminVdc) RemoveStorageProfile(storageProfileName string) (Task, error) { + if vdc.client.VCDHREF.String() == "" { + return Task{}, fmt.Errorf("cannot remove VDC storage profile: VCD HREF is unset") + } + + var storageProfile *types.Reference + for _, sp := range vdc.AdminVdc.VdcStorageProfiles.VdcStorageProfile { + if sp.Name == storageProfileName { + storageProfile = sp + } + } + if storageProfile == nil { + return Task{}, fmt.Errorf("cannot remove VDC storage profile: storage profile '%s' not found in VDC", storageProfileName) + } + + vdcStorageProfileDetails, err := vdc.client.GetStorageProfileByHref(storageProfile.HREF) + if err != nil { + return Task{}, fmt.Errorf("cannot retrieve VDC storage profile '%s' details: %s", storageProfileName, err) + } + if vdcStorageProfileDetails.Enabled { + _, err = vdc.UpdateStorageProfile(extractUuid(storageProfile.HREF), &types.AdminVdcStorageProfile{ + Name: vdcStorageProfileDetails.Name, + Units: vdcStorageProfileDetails.Units, + Limit: vdcStorageProfileDetails.Limit, + Default: false, + Enabled: takeBoolPointer(false), + ProviderVdcStorageProfile: &types.Reference{ + HREF: vdcStorageProfileDetails.ProviderVdcStorageProfile.HREF, + }, + }, + ) + if err != nil { + return Task{}, fmt.Errorf("cannot disable VDC storage profile '%s': %s", storageProfileName, err) + } + } + + href := vdc.AdminVdc.HREF + "/vdcStorageProfiles" + + var updateStorageProfile = types.UpdateVdcStorageProfiles{ + Xmlns: types.XMLNamespaceVCloud, + Name: vdcStorageProfileDetails.Name, + Description: "", + RemoveStorageProfile: storageProfile, + } + + task, err := vdc.client.ExecuteTaskRequest(href, http.MethodPost, + types.MimeUpdateVdcStorageProfiles, "error removing VDC storage profile: %s", &updateStorageProfile) + if err != nil { + return Task{}, fmt.Errorf("cannot remove VDC storage profile, error: %s", err) + } + + return task, nil +} + +// RemoveStorageProfileWait removes a storege profile from a VDC and returns a refreshed VDC or an error +func (vdc *AdminVdc) RemoveStorageProfileWait(storageProfileName string) error { + task, err := vdc.RemoveStorageProfile(storageProfileName) + if err != nil { + return err + } + err = task.WaitTaskCompletion() + if err != nil { + return err + } + return vdc.Refresh() +} + +// SetDefaultStorageProfile sets a given storage profile as default +// This operation will automatically unset the previous default storage profile. +func (vdc *AdminVdc) SetDefaultStorageProfile(storageProfileName string) error { + if vdc.client.VCDHREF.String() == "" { + return fmt.Errorf("cannot set VDC default storage profile: VCD HREF is unset") + } + + var storageProfile *types.Reference + for _, sp := range vdc.AdminVdc.VdcStorageProfiles.VdcStorageProfile { + if sp.Name == storageProfileName { + storageProfile = sp + } + } + if storageProfile == nil { + return fmt.Errorf("cannot set VDC default storage profile: storage profile '%s' not found in VDC", storageProfileName) + } + + vdcStorageProfileDetails, err := vdc.client.GetStorageProfileByHref(storageProfile.HREF) + if err != nil { + return fmt.Errorf("cannot retrieve VDC storage profile '%s' details: %s", storageProfileName, err) + } + _, err = vdc.UpdateStorageProfile(extractUuid(storageProfile.HREF), &types.AdminVdcStorageProfile{ + Name: vdcStorageProfileDetails.Name, + Units: vdcStorageProfileDetails.Units, + Limit: vdcStorageProfileDetails.Limit, + Default: true, + Enabled: takeBoolPointer(true), + ProviderVdcStorageProfile: &types.Reference{ + HREF: vdcStorageProfileDetails.ProviderVdcStorageProfile.HREF, + }, + }, + ) + if err != nil { + return fmt.Errorf("cannot set VDC default storage profile '%s': %s", storageProfileName, err) + } + return vdc.Refresh() +} + +// GetDefaultStorageProfileReference finds the default storage profile for the VDC +func (adminVdc *AdminVdc) GetDefaultStorageProfileReference() (*types.Reference, error) { + var defaultSp *types.Reference + if adminVdc.AdminVdc.VdcStorageProfiles == nil || adminVdc.AdminVdc.VdcStorageProfiles.VdcStorageProfile == nil { + return nil, fmt.Errorf("no storage profiles found in VDC %s", adminVdc.AdminVdc.Name) + } + for _, sp := range adminVdc.AdminVdc.VdcStorageProfiles.VdcStorageProfile { + fullSp, err := adminVdc.client.GetStorageProfileByHref(sp.HREF) + if err != nil { + return nil, fmt.Errorf("error retrieving storage profile %s for VDC %s: %s", sp.Name, adminVdc.AdminVdc.Name, err) + } + if fullSp.Default { + if defaultSp != nil { + return nil, fmt.Errorf("more than one default storage profile found for VDC %s: '%s' and '%s'", adminVdc.AdminVdc.Name, sp.Name, defaultSp.Name) + } + defaultSp = sp + } + } + if defaultSp != nil { + return defaultSp, nil + } + return nil, fmt.Errorf("no default storage profile found for VDC %s", adminVdc.AdminVdc.Name) +} diff --git a/govcd/adminvdc_nsxt_test.go b/govcd/adminvdc_nsxt_test.go index f289634c3..e18857bae 100644 --- a/govcd/adminvdc_nsxt_test.go +++ b/govcd/adminvdc_nsxt_test.go @@ -34,13 +34,10 @@ func (vcd *TestVCD) Test_CreateNsxtOrgVdc(check *C) { } providerVdcHref := pVdcs[0].HREF - pvdcStorageProfiles, err := QueryProviderVdcStorageProfileByName(vcd.client, vcd.config.VCD.NsxtProviderVdc.StorageProfile) + pvdcStorageProfile, err := vcd.client.QueryProviderVdcStorageProfileByName(vcd.config.VCD.NsxtProviderVdc.StorageProfile, providerVdcHref) check.Assert(err, IsNil) - if len(pvdcStorageProfiles) == 0 { - check.Skip(fmt.Sprintf("No storage profile found with name '%s'", vcd.config.VCD.NsxtProviderVdc.StorageProfile)) - } - providerVdcStorageProfileHref := pvdcStorageProfiles[0].HREF + providerVdcStorageProfileHref := pvdcStorageProfile.HREF networkPools, err := QueryNetworkPoolByName(vcd.client, vcd.config.VCD.NsxtProviderVdc.NetworkPool) check.Assert(err, IsNil) diff --git a/govcd/adminvdc_test.go b/govcd/adminvdc_test.go index 18f65e0d8..a7c6cde55 100644 --- a/govcd/adminvdc_test.go +++ b/govcd/adminvdc_test.go @@ -39,7 +39,10 @@ func (vcd *TestVCD) Test_CreateOrgVdcWithFlex(check *C) { check.Assert(adminOrg, NotNil) providerVdcHref := getVdcProviderVdcHref(vcd, check) - providerVdcStorageProfileHref := getVdcProviderVdcStorageProfileHref(vcd, check) + + storageProfile, err := vcd.client.QueryProviderVdcStorageProfileByName(vcd.config.VCD.ProviderVdc.StorageProfile, providerVdcHref) + check.Assert(err, IsNil) + providerVdcStorageProfileHref := storageProfile.HREF networkPoolHref := getVdcNetworkPoolHref(vcd, check) allocationModels := []string{"AllocationVApp", "AllocationPool", "ReservationPool", "Flex"} @@ -208,7 +211,7 @@ func (vcd *TestVCD) Test_VdcUpdateStorageProfile(check *C) { check.Assert(err, IsNil) check.Assert(adminVdc, NotNil) - foundStorageProfile, err := GetStorageProfileByHref(vcd.client, adminVdc.AdminVdc.VdcStorageProfiles.VdcStorageProfile[0].HREF) + foundStorageProfile, err := vcd.client.Client.GetStorageProfileByHref(adminVdc.AdminVdc.VdcStorageProfiles.VdcStorageProfile[0].HREF) check.Assert(err, IsNil) check.Assert(foundStorageProfile, Not(Equals), types.VdcStorageProfile{}) check.Assert(foundStorageProfile, NotNil) @@ -229,7 +232,7 @@ func (vcd *TestVCD) Test_VdcUpdateStorageProfile(check *C) { check.Assert(err, IsNil) check.Assert(updatedVdc, Not(IsNil)) - updatedStorageProfile, err := GetStorageProfileByHref(vcd.client, adminVdc.AdminVdc.VdcStorageProfiles.VdcStorageProfile[0].HREF) + updatedStorageProfile, err := vcd.client.Client.GetStorageProfileByHref(adminVdc.AdminVdc.VdcStorageProfiles.VdcStorageProfile[0].HREF) check.Assert(err, IsNil) check.Assert(updatedStorageProfile, Not(Equals), types.VdcStorageProfile{}) check.Assert(updatedStorageProfile, NotNil) diff --git a/govcd/catalog.go b/govcd/catalog.go index 32c9b912a..ec1f9310a 100644 --- a/govcd/catalog.go +++ b/govcd/catalog.go @@ -43,7 +43,7 @@ func NewCatalog(client *Client) *Catalog { } // Delete deletes the Catalog, returning an error if the vCD call fails. -// Link to API call: https://code.vmware.com/apis/220/vcloud#/doc/doc/operations/DELETE-Catalog.html +// Link to API call: https://code.vmware.com/apis/1046/vmware-cloud-director/doc/doc/operations/DELETE-Catalog.html func (catalog *Catalog) Delete(force, recursive bool) error { adminCatalogHREF := catalog.client.VCDHREF diff --git a/govcd/catalogitem_test.go b/govcd/catalogitem_test.go index 1bfa8cc82..d885535e2 100644 --- a/govcd/catalogitem_test.go +++ b/govcd/catalogitem_test.go @@ -192,3 +192,39 @@ func (vcd *TestVCD) TestQueryCatalogItemAndVAppTemplateList(check *C) { } } + +func (vcd *TestVCD) Test_DeleteNonEmptyCatalog(check *C) { + skipWhenOvaPathMissing(vcd.config.OVA.OvaPath, check) + + catalogName := check.TestName() + catalogItemName := check.TestName() + "_item" + // Fetching organization + org, err := vcd.client.GetAdminOrgByName(vcd.org.Org.Name) + check.Assert(err, IsNil) + check.Assert(org, NotNil) + catalog, err := org.CreateCatalog(catalogName, catalogName) + check.Assert(err, IsNil) + AddToCleanupList(catalogName, "catalog", vcd.org.Org.Name, check.TestName()) + + check.Assert(catalog, NotNil) + + // add catalogItem + uploadTask, err := catalog.UploadOvf(vcd.config.OVA.OvaPath, catalogItemName, "upload from delete catalog item test", 1024) + check.Assert(err, IsNil) + err = uploadTask.WaitTaskCompletion() + check.Assert(err, IsNil) + AddToCleanupList(catalogItemName, "catalogItem", vcd.org.Org.Name+"|"+catalogName, check.TestName()) + + retrievedCatalog, err := org.GetCatalogByName(catalogName, true) + check.Assert(err, IsNil) + catalogItem, err := retrievedCatalog.GetCatalogItemByName(catalogItemName, true) + check.Assert(err, IsNil) + check.Assert(catalogItem, NotNil) + + err = retrievedCatalog.Delete(true, true) + check.Assert(err, IsNil) + + retrievedCatalog, err = org.GetCatalogByName(catalogName, true) + check.Assert(err, NotNil) + check.Assert(retrievedCatalog, IsNil) +} diff --git a/govcd/common_test.go b/govcd/common_test.go index 09dd3ac43..8dcc4dc82 100644 --- a/govcd/common_test.go +++ b/govcd/common_test.go @@ -740,7 +740,8 @@ func spawnTestVdc(vcd *TestVCD, check *C, adminOrgName string) *Vdc { check.Assert(err, IsNil) providerVdcHref := getVdcProviderVdcHref(vcd, check) - providerVdcStorageProfileHref := getVdcProviderVdcStorageProfileHref(vcd, check) + storageProfile, err := vcd.client.QueryProviderVdcStorageProfileByName(vcd.config.VCD.ProviderVdc.StorageProfile, providerVdcHref) + check.Assert(err, IsNil) networkPoolHref := getVdcNetworkPoolHref(vcd, check) vdcConfiguration := &types.VdcConfiguration{ @@ -767,7 +768,7 @@ func spawnTestVdc(vcd *TestVCD, check *C, adminOrgName string) *Vdc { Limit: 1024, Default: true, ProviderVdcStorageProfile: &types.Reference{ - HREF: providerVdcStorageProfileHref, + HREF: storageProfile.HREF, }, }, }, @@ -821,20 +822,6 @@ func getVdcProviderVdcHref(vcd *TestVCD, check *C) string { return providerVdcHref } -func getVdcProviderVdcStorageProfileHref(vcd *TestVCD, check *C) string { - results, err := vcd.client.QueryWithNotEncodedParams(nil, map[string]string{ - "type": "providerVdcStorageProfile", - "filter": fmt.Sprintf("name==%s", vcd.config.VCD.ProviderVdc.StorageProfile), - }) - check.Assert(err, IsNil) - if len(results.Results.ProviderVdcStorageProfileRecord) == 0 { - check.Skip(fmt.Sprintf("No storage profile found with name '%s'", vcd.config.VCD.ProviderVdc.StorageProfile)) - } - providerVdcStorageProfileHref := results.Results.ProviderVdcStorageProfileRecord[0].HREF - - return providerVdcStorageProfileHref -} - func getVdcNetworkPoolHref(vcd *TestVCD, check *C) string { results, err := vcd.client.QueryWithNotEncodedParams(nil, map[string]string{ "type": "networkPool", diff --git a/govcd/org_test.go b/govcd/org_test.go index b2c9c535e..5f0cf4815 100644 --- a/govcd/org_test.go +++ b/govcd/org_test.go @@ -249,15 +249,8 @@ func (vcd *TestVCD) Test_CreateVdc(check *C) { } providerVdcHref := results.Results.VMWProviderVdcRecord[0].HREF - results, err = vcd.client.QueryWithNotEncodedParams(nil, map[string]string{ - "type": "providerVdcStorageProfile", - "filter": fmt.Sprintf("name==%s", vcd.config.VCD.ProviderVdc.StorageProfile), - }) + storageProfile, err := vcd.client.QueryProviderVdcStorageProfileByName(vcd.config.VCD.ProviderVdc.StorageProfile, providerVdcHref) check.Assert(err, IsNil) - if len(results.Results.ProviderVdcStorageProfileRecord) == 0 { - check.Skip(fmt.Sprintf("No storage profile found with name '%s'", vcd.config.VCD.ProviderVdc.StorageProfile)) - } - providerVdcStorageProfileHref := results.Results.ProviderVdcStorageProfileRecord[0].HREF results, err = vcd.client.QueryWithNotEncodedParams(nil, map[string]string{ "type": "networkPool", @@ -293,7 +286,7 @@ func (vcd *TestVCD) Test_CreateVdc(check *C) { Limit: 1024, Default: true, ProviderVdcStorageProfile: &types.Reference{ - HREF: providerVdcStorageProfileHref, + HREF: storageProfile.HREF, }, }, }, @@ -626,15 +619,10 @@ func setupVdc(vcd *TestVCD, check *C, allocationModel string) (AdminOrg, *types. check.Skip(fmt.Sprintf("No Provider VDC found with name '%s'", vcd.config.VCD.ProviderVdc.Name)) } providerVdcHref := results.Results.VMWProviderVdcRecord[0].HREF - results, err = vcd.client.QueryWithNotEncodedParams(nil, map[string]string{ - "type": "providerVdcStorageProfile", - "filter": fmt.Sprintf("name==%s", vcd.config.VCD.ProviderVdc.StorageProfile), - }) + storageProfile, err := vcd.client.QueryProviderVdcStorageProfileByName(vcd.config.VCD.ProviderVdc.StorageProfile, providerVdcHref) check.Assert(err, IsNil) - if len(results.Results.ProviderVdcStorageProfileRecord) == 0 { - check.Skip(fmt.Sprintf("No storage profile found with name '%s'", vcd.config.VCD.ProviderVdc.StorageProfile)) - } - providerVdcStorageProfileHref := results.Results.ProviderVdcStorageProfileRecord[0].HREF + + check.Assert(storageProfile.HREF, Not(Equals), "") results, err = vcd.client.QueryWithNotEncodedParams(nil, map[string]string{ "type": "networkPool", "filter": fmt.Sprintf("name==%s", vcd.config.VCD.ProviderVdc.NetworkPool), @@ -645,7 +633,7 @@ func setupVdc(vcd *TestVCD, check *C, allocationModel string) (AdminOrg, *types. } networkPoolHref := results.Results.NetworkPoolRecord[0].HREF vdcConfiguration := &types.VdcConfiguration{ - Name: TestCreateOrgVdc + "ForRefresh", + Name: check.TestName(), AllocationModel: allocationModel, ComputeCapacity: []*types.ComputeCapacity{ &types.ComputeCapacity{ @@ -667,7 +655,7 @@ func setupVdc(vcd *TestVCD, check *C, allocationModel string) (AdminOrg, *types. Limit: 1024, Default: true, ProviderVdcStorageProfile: &types.Reference{ - HREF: providerVdcStorageProfileHref, + HREF: storageProfile.HREF, }, }, }, @@ -699,6 +687,206 @@ func setupVdc(vcd *TestVCD, check *C, allocationModel string) (AdminOrg, *types. return *adminOrg, vdcConfiguration, err } +func (vcd *TestVCD) Test_QueryStorageProfiles(check *C) { + + // retrieve Org and VDC + adminOrg, err := vcd.client.GetAdminOrgByName(vcd.config.VCD.Org) + check.Assert(err, IsNil) + adminVdc, err := adminOrg.GetAdminVDCByName(vcd.config.VCD.Vdc, false) + check.Assert(err, IsNil) + + // Gets the Provider VDC from the AdminVdc structure + providerVdcName := adminVdc.AdminVdc.ProviderVdcReference.Name + check.Assert(providerVdcName, Not(Equals), "") + providerVdcHref := adminVdc.AdminVdc.ProviderVdcReference.HREF + check.Assert(providerVdcHref, Not(Equals), "") + + // Gets the full list of storage profilers + rawSpList, err := vcd.client.Client.QueryAllProviderVdcStorageProfiles() + check.Assert(err, IsNil) + + // Manually select the storage profiles that belong to the current provider VDC + var spList []*types.QueryResultProviderVdcStorageProfileRecordType + var duplicateNames = make(map[string]bool) + var notLocalStorageProfile string + var used = make(map[string]bool) + for _, sp := range rawSpList { + if sp.ProviderVdcHREF == providerVdcHref { + spList = append(spList, sp) + } + _, seen := used[sp.Name] + if seen { + duplicateNames[sp.Name] = true + } + used[sp.Name] = true + } + // Find a storage profile from a different provider VDC + for _, sp := range rawSpList { + if sp.ProviderVdcHREF != providerVdcHref { + _, isDuplicate := duplicateNames[sp.Name] + if !isDuplicate { + notLocalStorageProfile = sp.Name + } + } + } + + // Get the list of local storage profiles (belonging to the Provider VDC that the adminVdc depends on) + localSpList, err := vcd.client.Client.QueryProviderVdcStorageProfiles(providerVdcHref) + check.Assert(err, IsNil) + // Make sure the automated list and the manual list match + check.Assert(spList, DeepEquals, localSpList) + + // Get the same list using the AdminVdc method and check that the result matches + compatibleSpList, err := adminVdc.QueryCompatibleStorageProfiles() + check.Assert(err, IsNil) + check.Assert(compatibleSpList, DeepEquals, localSpList) + + for _, sp := range compatibleSpList { + fullSp, err := vcd.client.QueryProviderVdcStorageProfileByName(sp.Name, providerVdcHref) + check.Assert(err, IsNil) + check.Assert(sp.HREF, Equals, fullSp.HREF) + check.Assert(fullSp.ProviderVdcHREF, Equals, providerVdcHref) + } + + // When we have duplicate names, we also check the effectiveness of the retrieval function with Provider VDC filter + for name := range duplicateNames { + // Duplicate name with specific provider VDC HREF will succeed + fullSp, err := vcd.client.QueryProviderVdcStorageProfileByName(name, providerVdcHref) + check.Assert(err, IsNil) + check.Assert(fullSp.ProviderVdcHREF, Equals, providerVdcHref) + // Duplicate name with empty provider VDC HREF will fail + faultySp, err := vcd.client.QueryProviderVdcStorageProfileByName(name, "") + check.Assert(err, NotNil) + check.Assert(faultySp, IsNil) + } + + // Search explicitly for a storage profile not present in current provider VDC + if notLocalStorageProfile != "" { + fullSp, err := vcd.client.QueryProviderVdcStorageProfileByName(notLocalStorageProfile, providerVdcHref) + check.Assert(err, NotNil) + check.Assert(fullSp, IsNil) + } +} + +func (vcd *TestVCD) Test_AddRemoveVdcStorageProfiles(check *C) { + + if vcd.config.VCD.ProviderVdc.Name == "" { + check.Skip("No provider VDC found in configuration") + } + providerVDCs, err := QueryProviderVdcByName(vcd.client, vcd.config.VCD.ProviderVdc.Name) + check.Assert(err, IsNil) + check.Assert(len(providerVDCs), Equals, 1) + + rawSpList, err := vcd.client.Client.QueryAllProviderVdcStorageProfiles() + check.Assert(err, IsNil) + var spList []*types.QueryResultProviderVdcStorageProfileRecordType + for _, sp := range rawSpList { + if sp.ProviderVdcHREF == providerVDCs[0].HREF { + spList = append(spList, sp) + } + } + + localSpList, err := vcd.client.Client.QueryProviderVdcStorageProfiles(providerVDCs[0].HREF) + check.Assert(err, IsNil) + check.Assert(spList, DeepEquals, localSpList) + + const minSp = 2 + if len(spList) < minSp { + check.Skip(fmt.Sprintf("At least %d storage profiles are needed for this test", minSp)) + } + var defaultSp *types.QueryResultProviderVdcStorageProfileRecordType + var sp2 *types.QueryResultProviderVdcStorageProfileRecordType + + for i := 0; i < minSp; i++ { + if spList[i].Name == vcd.config.VCD.ProviderVdc.StorageProfile { + if defaultSp == nil { + defaultSp = spList[i] + } + } else { + if sp2 == nil { + sp2 = spList[i] + } + } + } + + check.Assert(defaultSp, NotNil) + check.Assert(sp2, NotNil) + + // Create the VDC + adminOrg, vdcConfiguration, err := setupVdc(vcd, check, "AllocationPool") + check.Assert(err, IsNil) + + adminVdc, err := adminOrg.GetAdminVDCByName(vdcConfiguration.Name, true) + check.Assert(err, IsNil) + + // Add another storage profile + err = adminVdc.AddStorageProfileWait(&types.VdcStorageProfileConfiguration{ + Enabled: true, + Units: "MB", + Limit: 1024, + Default: false, + ProviderVdcStorageProfile: &types.Reference{ + HREF: sp2.HREF, + Name: sp2.Name, + }, + }, "new sp 2") + check.Assert(err, IsNil) + check.Assert(len(adminVdc.AdminVdc.VdcStorageProfiles.VdcStorageProfile), Equals, 2) + + // Find the default storage profile and makes sure it matches with the one we know to be the default + defaultSpRef, err := adminVdc.GetDefaultStorageProfileReference() + check.Assert(err, IsNil) + check.Assert(defaultSp.Name, Equals, defaultSpRef.Name) + + // Remove the second storage profile + err = adminVdc.RemoveStorageProfileWait(sp2.Name) + check.Assert(err, IsNil) + check.Assert(len(adminVdc.AdminVdc.VdcStorageProfiles.VdcStorageProfile), Equals, 1) + + // Add the second storage profile again + err = adminVdc.AddStorageProfileWait(&types.VdcStorageProfileConfiguration{ + Enabled: true, + Units: "MB", + Limit: 1024, + Default: false, + ProviderVdcStorageProfile: &types.Reference{ + HREF: sp2.HREF, + Name: sp2.Name, + }, + }, "new sp 2") + + check.Assert(err, IsNil) + check.Assert(len(adminVdc.AdminVdc.VdcStorageProfiles.VdcStorageProfile), Equals, 2) + + // Change default storage profile from the original one to the second one + err = adminVdc.SetDefaultStorageProfile(sp2.Name) + check.Assert(err, IsNil) + + // Check that the default storage profile was changed + defaultSpRef, err = adminVdc.GetDefaultStorageProfileReference() + check.Assert(err, IsNil) + check.Assert(defaultSpRef.Name, Equals, sp2.Name) + + // Set the default storage profile again to the same item. + // This proves that SetDefaultStorageProfile is idempotent + err = adminVdc.SetDefaultStorageProfile(sp2.Name) + check.Assert(err, IsNil) + defaultSpRef, err = adminVdc.GetDefaultStorageProfileReference() + check.Assert(err, IsNil) + check.Assert(defaultSpRef.Name, Equals, sp2.Name) + + // Remove the former default storage profile + err = adminVdc.RemoveStorageProfileWait(defaultSp.Name) + check.Assert(err, IsNil) + check.Assert(len(adminVdc.AdminVdc.VdcStorageProfiles.VdcStorageProfile), Equals, 1) + + // Delete the VDC + vdc, err := adminOrg.GetVDCByName(adminVdc.AdminVdc.Name, false) + check.Assert(err, IsNil) + err = vdc.DeleteWait(true, true) + check.Assert(err, IsNil) +} + // Tests VDC by updating it and then asserting if the // variable is updated. func (vcd *TestVCD) Test_UpdateVdc(check *C) { diff --git a/govcd/system.go b/govcd/system.go index 7b8a5a5f6..013419fe8 100644 --- a/govcd/system.go +++ b/govcd/system.go @@ -717,11 +717,22 @@ func getExtension(client *Client) (*types.Extension, error) { } // GetStorageProfileByHref fetches storage profile using provided HREF. +// Deprecated: use client.GetStorageProfileByHref or vcdClient.GetStorageProfileByHref func GetStorageProfileByHref(vcdClient *VCDClient, url string) (*types.VdcStorageProfile, error) { + return vcdClient.Client.GetStorageProfileByHref(url) +} + +// GetStorageProfileByHref fetches a storage profile using its HREF. +func (vcdClient *VCDClient) GetStorageProfileByHref(url string) (*types.VdcStorageProfile, error) { + return vcdClient.Client.GetStorageProfileByHref(url) +} + +// GetStorageProfileByHref fetches a storage profile using its HREF. +func (client *Client) GetStorageProfileByHref(url string) (*types.VdcStorageProfile, error) { vdcStorageProfile := &types.VdcStorageProfile{} - _, err := vcdClient.Client.ExecuteRequest(url, http.MethodGet, + _, err := client.ExecuteRequest(url, http.MethodGet, "", "error retrieving storage profile: %s", nil, vdcStorageProfile) if err != nil { return nil, err @@ -731,6 +742,56 @@ func GetStorageProfileByHref(vcdClient *VCDClient, url string) (*types.VdcStorag } // QueryProviderVdcStorageProfileByName finds a provider VDC storage profile by name +// There are four cases: +// 1. [FOUND] The name matches and is unique among all the storage profiles +// 2. [FOUND] The name matches, it is not unique, and it is disambiguated by the provider VDC HREF +// 3. [NOT FOUND] The name matches, is not unique, but no Provider HREF was given: the search will fail +// 4. [NOT FOUND] The name does not match any of the storage profiles +func (vcdCli *VCDClient) QueryProviderVdcStorageProfileByName(name, providerVDCHref string) (*types.QueryResultProviderVdcStorageProfileRecordType, error) { + results, err := vcdCli.QueryWithNotEncodedParams(nil, map[string]string{ + "type": "providerVdcStorageProfile", + "pageSize": "128", + }) + if err != nil { + return nil, err + } + + // Note: pageSize of 128 (the maximum page size allowed) should be enough to get all storage profiles. + // In case this is not true, we trap the error, so that we become aware that this assumption is incorrect. + // TODO: convert this query into a cumulativeQuery + if results.Results.Total > 128.0 { + return nil, fmt.Errorf("[QueryWithNotEncodedParams] FATAL - more than 128 storage profiles found. Refactory needed") + } + + var recs []*types.QueryResultProviderVdcStorageProfileRecordType + for _, rec := range results.Results.ProviderVdcStorageProfileRecord { + if rec.Name == name { + // Double match: both the name and the provider VDC match: we can return the result + if providerVDCHref != "" && providerVDCHref == rec.ProviderVdcHREF { + return rec, nil + } + // if there is a name match, but no provider VDC was given, we add to the result, and we will check later. + if providerVDCHref == "" { + recs = append(recs, rec) + } + } + } + + providerVDCMessage := "" + if providerVDCHref != "" { + providerVDCMessage = fmt.Sprintf("in provider VDC '%s'", providerVDCHref) + } + if len(recs) == 0 { + return nil, fmt.Errorf("no records found for storage profile '%s' %s", name, providerVDCMessage) + } + if len(recs) > 1 { + return nil, fmt.Errorf("more than 1 record found for storage profile '%s'. Add Provider VDC HREF in the search to disambiguate", name) + } + return recs[0], nil +} + +// QueryProviderVdcStorageProfileByName finds a provider VDC storage profile by name +// Deprecated: wrong implementation. Use VCDClient.QueryProviderVdcStorageProfileByName func QueryProviderVdcStorageProfileByName(vcdCli *VCDClient, name string) ([]*types.QueryResultProviderVdcStorageProfileRecordType, error) { results, err := vcdCli.QueryWithNotEncodedParams(nil, map[string]string{ "type": "providerVdcStorageProfile", @@ -796,9 +857,15 @@ func (vcdClient *VCDClient) QueryNetworkPools() ([]*types.QueryResultNetworkPool return results.Results.NetworkPoolRecord, nil } -// QueryProviderVdcStorageProfiles gets the list of provider VDC storage profiles +// QueryProviderVdcStorageProfiles gets the list of provider VDC storage profiles from ALL provider VDCs +// Deprecated: use either client.QueryProviderVdcStorageProfiles or client.QueryAllProviderVdcStorageProfiles func (vcdClient *VCDClient) QueryProviderVdcStorageProfiles() ([]*types.QueryResultProviderVdcStorageProfileRecordType, error) { - results, err := vcdClient.QueryWithNotEncodedParams(nil, map[string]string{ + return vcdClient.Client.QueryAllProviderVdcStorageProfiles() +} + +// QueryAllProviderVdcStorageProfiles gets the list of provider VDC storage profiles from ALL provider VDCs +func (client *Client) QueryAllProviderVdcStorageProfiles() ([]*types.QueryResultProviderVdcStorageProfileRecordType, error) { + results, err := client.QueryWithNotEncodedParams(nil, map[string]string{ "type": "providerVdcStorageProfile", }) if err != nil { @@ -808,6 +875,25 @@ func (vcdClient *VCDClient) QueryProviderVdcStorageProfiles() ([]*types.QueryRes return results.Results.ProviderVdcStorageProfileRecord, nil } +// QueryProviderVdcStorageProfiles gets the list of provider VDC storage profiles for a given Provider VDC +func (client *Client) QueryProviderVdcStorageProfiles(providerVdcHref string) ([]*types.QueryResultProviderVdcStorageProfileRecordType, error) { + results, err := client.QueryWithNotEncodedParams(nil, map[string]string{ + "type": "providerVdcStorageProfile", + "filter": fmt.Sprintf("providerVdc==%s", providerVdcHref), + }) + if err != nil { + return nil, err + } + + return results.Results.ProviderVdcStorageProfileRecord, nil +} + +// QueryCompatibleStorageProfiles retrieves all storage profiles belonging to the same provider VDC to which +// the Org VDC belongs +func (adminVdc *AdminVdc) QueryCompatibleStorageProfiles() ([]*types.QueryResultProviderVdcStorageProfileRecordType, error) { + return adminVdc.client.QueryProviderVdcStorageProfiles(adminVdc.AdminVdc.ProviderVdcReference.HREF) +} + // GetNetworkPoolByHREF functions fetches an network pool using VDC client and network pool href func GetNetworkPoolByHREF(client *VCDClient, href string) (*types.VMWNetworkPool, error) { util.Logger.Printf("[TRACE] Get network pool by HREF: %s\n", href) diff --git a/govcd/system_test.go b/govcd/system_test.go index c8cca0bad..55b7f4b44 100644 --- a/govcd/system_test.go +++ b/govcd/system_test.go @@ -520,7 +520,7 @@ func (vcd *TestVCD) Test_QueryProviderVdcEntities(check *C) { if storageProfileName == "" { check.Skip("Skipping storage profile query: no storage profile was given") } - storageProfiles, err := vcd.client.QueryProviderVdcStorageProfiles() + storageProfiles, err := vcd.client.Client.QueryAllProviderVdcStorageProfiles() check.Assert(err, IsNil) check.Assert(len(storageProfiles) > 0, Equals, true) storageProfileFound := false @@ -609,7 +609,7 @@ func (vcd *TestVCD) Test_GetStorageProfileByHref(check *C) { check.Assert(adminVdc, NotNil) // Get storage profile by href - foundStorageProfile, err := GetStorageProfileByHref(vcd.client, adminVdc.AdminVdc.VdcStorageProfiles.VdcStorageProfile[0].HREF) + foundStorageProfile, err := vcd.client.Client.GetStorageProfileByHref(adminVdc.AdminVdc.VdcStorageProfiles.VdcStorageProfile[0].HREF) check.Assert(err, IsNil) check.Assert(foundStorageProfile, Not(Equals), types.VdcStorageProfile{}) check.Assert(foundStorageProfile, NotNil) diff --git a/govcd/vdc.go b/govcd/vdc.go index b251e6d53..98613da6f 100644 --- a/govcd/vdc.go +++ b/govcd/vdc.go @@ -278,6 +278,8 @@ func (vdc *Vdc) FindStorageProfileReference(name string) (types.Reference, error return types.Reference{}, fmt.Errorf("can't find any VDC Storage_profiles") } +// GetDefaultStorageProfileReference should find the default storage profile for a VDC +// Deprecated: unused and implemented in the wrong way. Use adminVdc.GetDefaultStorageProfileReference instead func (vdc *Vdc) GetDefaultStorageProfileReference(storageprofiles *types.QueryResultRecordsType) (types.Reference, error) { err := vdc.Refresh() diff --git a/types/v56/constants.go b/types/v56/constants.go index 0d92b8ff7..8fcb7830a 100644 --- a/types/v56/constants.go +++ b/types/v56/constants.go @@ -131,6 +131,8 @@ const ( MimeCreateVmParams = "application/vnd.vmware.vcloud.CreateVmParams+xml" // Mime for instantiate VM Params from template MimeInstantiateVmTemplateParams = "application/vnd.vmware.vcloud.instantiateVmTemplateParams+xml" + // Mime for adding or removing VDC storage profiles + MimeUpdateVdcStorageProfiles = "application/vnd.vmware.admin.updateVdcStorageProfiles+xml" ) const ( diff --git a/types/v56/types.go b/types/v56/types.go index ee3849491..39abc760c 100644 --- a/types/v56/types.go +++ b/types/v56/types.go @@ -2965,3 +2965,13 @@ type VCloud struct { // RoleReferences // Networks } + +// UpdateVdcStorageProfiles is used to add a storage profile to an Org VDC or to remove one +type UpdateVdcStorageProfiles struct { + XMLName xml.Name `xml:"UpdateVdcStorageProfiles"` + Xmlns string `xml:"xmlns,attr,omitempty"` + Name string `xml:"name,attr"` + Description string `xml:"Description,omitempty"` + AddStorageProfile *VdcStorageProfileConfiguration `xml:"AddStorageProfile,omitempty"` + RemoveStorageProfile *Reference `xml:"RemoveStorageProfile,omitempty"` +}