github.com/vmware/go-vcloud-director/v2@v2.24.0/govcd/query_metadata.go (about) 1 /* 2 * Copyright 2020 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. 3 */ 4 5 package govcd 6 7 import ( 8 "fmt" 9 "net/url" 10 "strings" 11 12 "github.com/vmware/go-vcloud-director/v2/types/v56" 13 ) 14 15 /* 16 This file contains functions that allow an extended query including metadata fields. 17 18 The metadata fields need to be requested explicitly (we can't just ask for generic metadata to be included 19 in the query result. Due to the query system implementation, when we request metadata fields, we must also 20 list the regular fields that we want in the result. 21 For this reason, we need to have the list of fields supported by the query for each query type. Not all the 22 fields can be used in the "fields" parameter of the query. 23 24 The function queryFieldsOnDemand provides the fields for the supported types. 25 26 Example: we have type "X" with fields "a", "b", "c", "d". It supports metadata. 27 If we want to query X without metadata, we run a simple query?type=X;[...] 28 29 If we also want metadata, we need to know which keys we want to fetch, and run 30 query?type=X;fields=a,b,c,d,metadata:key1,metadata:key2 31 32 */ 33 34 // MetadataFilter is a definition of a value used to filter metadata. 35 // It is made of a Type (such as 'STRING', 'INT', 'BOOL") and a Value, which is the value we want to search for. 36 type MetadataFilter struct { 37 Type string 38 Value string 39 } 40 41 // queryFieldsOnDemand returns the list of fields that can be requested in the option "fields" of a query 42 // Note that an alternative approach using `reflect` would require several exceptions to list all the 43 // fields that are not supported. 44 func queryFieldsOnDemand(queryType string) ([]string, error) { 45 // entities for which the fields on demand are supported 46 var ( 47 vappTemplatefields = []string{"ownerName", "catalogName", "isPublished", "name", "vdc", "vdcName", 48 "org", "creationDate", "isBusy", "isGoldMaster", "isEnabled", "status", "isDeployed", "isExpired", 49 "storageProfileName"} 50 edgeGatewayFields = []string{"name", "vdc", "orgVdcName", "numberOfExtNetworks", "numberOfOrgNetworks", "isBusy", 51 "gatewayStatus", "haStatus"} 52 orgVdcNetworkFields = []string{"name", "defaultGateway", "netmask", "dns1", "dns2", "dnsSuffix", "linkType", 53 "connectedTo", "vdc", "isBusy", "isShared", "vdcName", "isIpScopeInherited"} 54 catalogFields = []string{"name", "isPublished", "isShared", "creationDate", "orgName", "ownerName", 55 "numberOfMedia", "owner"} 56 mediaFields = []string{"ownerName", "catalogName", "isPublished", "name", "vdc", "vdcName", "org", 57 "creationDate", "isBusy", "storageB", "owner", "catalog", "catalogItem", "status", 58 "storageProfileName", "taskStatusName", "isInCatalog", "task", 59 "isIso", "isVdcEnabled", "taskStatus", "taskDetails"} 60 catalogItemFields = []string{"entity", "entityName", "entityType", "catalog", "catalogName", "ownerName", 61 "owner", "isPublished", "vdc", "vdcName", "isVdcEnabled", "creationDate", "isExpired", "status"} 62 vmFields = []string{"catalogName", "container", "containerName", "datastoreName", "description", 63 "gcStatus", "guestOs", "hardwareVersion", "hostName", "isAutoNature", "isDeleted", "isDeployed", "isPublished", 64 "isVAppTemplate", "isVdcEnabled", "memoryMB", "moref", "name", "numberOfCpus", "org", "status", 65 "storageProfileName", "vc", "vdc", "vdcName", "vmToolsVersion", "containerStatus", "pvdcHighestSupportedHardwareVersion", 66 "isComputePolicyCompliant", "vmSizingPolicyId", "vmPlacementPolicyId", "encrypted", "dateCreated", 67 "totalStorageAllocatedMb", "isExpired"} 68 vappFields = []string{"creationDate", "isBusy", "isDeployed", "isEnabled", "isExpired", "isInMaintenanceMode", "isPublic", 69 "ownerName", "status", "vdc", "vdcName", "numberOfVMs", "numberOfCpus", "cpuAllocationMhz", "cpuAllocationInMhz", 70 "storageKB", "memoryAllocationMB", "isAutoDeleteNotified", "isAutoUndeployNotified", "isVdcEnabled", "honorBookOrder", 71 "pvdcHighestSupportedHardwareVersion", "lowestHardwareVersionInVApp"} 72 orgVdcFields = []string{"name", "description", "isEnabled", "cpuAllocationMhz", "cpuLimitMhz", "cpuUsedMhz", 73 "memoryAllocationMB", "memoryLimitMB", "memoryUsedMB", "storageLimitMB", "storageUsedMB", "providerVdcName", 74 "providerVdc", "orgName", "org", "allocationModel", "numberOfVApps", "numberOfUnmanagedVApps", "numberOfMedia", 75 "numberOfDisks", "numberOfVAppTemplates", "vcName", "isBusy", "status", "networkPool", "numberOfResourcePools", 76 "numberOfStorageProfiles", "usedNetworksInVdc", "numberOfVMs", "numberOfRunningVMs", "numberOfDeployedVApps", 77 "numberOfDeployedUnmanagedVApps", "isThinProvisioned", "isFastProvisioned", "networkProviderType", 78 "cpuOverheadMhz", "isVCEnabled", "memoryReservedMB", "cpuReservedMhz", "storageOverheadMB", "memoryOverheadMB", "vc"} 79 taskFields = []string{"href", "id", "type", "org", "orgName", "name", "operationFull", "message", "startDate", 80 "endDate", "status", "progress", "ownerName", "object", "objectType", "objectName", "serviceNamespace"} 81 fieldsOnDemand = map[string][]string{ 82 types.QtVappTemplate: vappTemplatefields, 83 types.QtAdminVappTemplate: vappTemplatefields, 84 types.QtEdgeGateway: edgeGatewayFields, 85 types.QtOrgVdcNetwork: orgVdcNetworkFields, 86 types.QtCatalog: catalogFields, 87 types.QtAdminCatalog: catalogFields, 88 types.QtMedia: mediaFields, 89 types.QtAdminMedia: mediaFields, 90 types.QtCatalogItem: catalogItemFields, 91 types.QtAdminCatalogItem: catalogItemFields, 92 types.QtVm: vmFields, 93 types.QtAdminVm: vmFields, 94 types.QtVapp: vappFields, 95 types.QtAdminVapp: vappFields, 96 types.QtOrgVdc: orgVdcFields, 97 types.QtAdminOrgVdc: orgVdcFields, 98 types.QtTask: taskFields, 99 types.QtAdminTask: taskFields, 100 } 101 ) 102 103 fields, ok := fieldsOnDemand[queryType] 104 if !ok { 105 return nil, fmt.Errorf("query type '%s' not supported", queryType) 106 } 107 return fields, nil 108 } 109 110 // addResults takes records from the appropriate field in the latest results and adds them to the cumulative results 111 func addResults(queryType string, cumulativeResults, newResults Results) (Results, int, error) { 112 113 var size int 114 switch queryType { 115 case types.QtAdminVappTemplate: 116 cumulativeResults.Results.AdminVappTemplateRecord = append(cumulativeResults.Results.AdminVappTemplateRecord, newResults.Results.AdminVappTemplateRecord...) 117 size = len(newResults.Results.AdminVappTemplateRecord) 118 case types.QtVappTemplate: 119 size = len(newResults.Results.VappTemplateRecord) 120 cumulativeResults.Results.VappTemplateRecord = append(cumulativeResults.Results.VappTemplateRecord, newResults.Results.VappTemplateRecord...) 121 case types.QtCatalogItem: 122 cumulativeResults.Results.CatalogItemRecord = append(cumulativeResults.Results.CatalogItemRecord, newResults.Results.CatalogItemRecord...) 123 size = len(newResults.Results.CatalogItemRecord) 124 case types.QtAdminCatalogItem: 125 cumulativeResults.Results.AdminCatalogItemRecord = append(cumulativeResults.Results.AdminCatalogItemRecord, newResults.Results.AdminCatalogItemRecord...) 126 size = len(newResults.Results.AdminCatalogItemRecord) 127 case types.QtMedia: 128 cumulativeResults.Results.MediaRecord = append(cumulativeResults.Results.MediaRecord, newResults.Results.MediaRecord...) 129 size = len(newResults.Results.MediaRecord) 130 case types.QtAdminMedia: 131 cumulativeResults.Results.AdminMediaRecord = append(cumulativeResults.Results.AdminMediaRecord, newResults.Results.AdminMediaRecord...) 132 size = len(newResults.Results.AdminMediaRecord) 133 case types.QtCatalog: 134 cumulativeResults.Results.CatalogRecord = append(cumulativeResults.Results.CatalogRecord, newResults.Results.CatalogRecord...) 135 size = len(newResults.Results.CatalogRecord) 136 case types.QtAdminCatalog: 137 cumulativeResults.Results.AdminCatalogRecord = append(cumulativeResults.Results.AdminCatalogRecord, newResults.Results.AdminCatalogRecord...) 138 size = len(newResults.Results.AdminCatalogRecord) 139 case types.QtOrgVdcNetwork: 140 cumulativeResults.Results.OrgVdcNetworkRecord = append(cumulativeResults.Results.OrgVdcNetworkRecord, newResults.Results.OrgVdcNetworkRecord...) 141 size = len(newResults.Results.OrgVdcNetworkRecord) 142 case types.QtEdgeGateway: 143 cumulativeResults.Results.EdgeGatewayRecord = append(cumulativeResults.Results.EdgeGatewayRecord, newResults.Results.EdgeGatewayRecord...) 144 size = len(newResults.Results.EdgeGatewayRecord) 145 case types.QtVm: 146 cumulativeResults.Results.VMRecord = append(cumulativeResults.Results.VMRecord, newResults.Results.VMRecord...) 147 size = len(newResults.Results.VMRecord) 148 case types.QtAdminVm: 149 cumulativeResults.Results.AdminVMRecord = append(cumulativeResults.Results.AdminVMRecord, newResults.Results.AdminVMRecord...) 150 size = len(newResults.Results.AdminVMRecord) 151 case types.QtVapp: 152 cumulativeResults.Results.VAppRecord = append(cumulativeResults.Results.VAppRecord, newResults.Results.VAppRecord...) 153 size = len(newResults.Results.VAppRecord) 154 case types.QtAdminVapp: 155 cumulativeResults.Results.AdminVAppRecord = append(cumulativeResults.Results.AdminVAppRecord, newResults.Results.AdminVAppRecord...) 156 size = len(newResults.Results.AdminVAppRecord) 157 case types.QtOrgVdc: 158 cumulativeResults.Results.OrgVdcRecord = append(cumulativeResults.Results.OrgVdcRecord, newResults.Results.OrgVdcRecord...) 159 size = len(newResults.Results.OrgVdcRecord) 160 case types.QtAdminOrgVdc: 161 cumulativeResults.Results.OrgVdcAdminRecord = append(cumulativeResults.Results.OrgVdcAdminRecord, newResults.Results.OrgVdcAdminRecord...) 162 size = len(newResults.Results.OrgVdcAdminRecord) 163 case types.QtTask: 164 cumulativeResults.Results.TaskRecord = append(cumulativeResults.Results.TaskRecord, newResults.Results.TaskRecord...) 165 size = len(newResults.Results.TaskRecord) 166 case types.QtAdminTask: 167 cumulativeResults.Results.AdminTaskRecord = append(cumulativeResults.Results.AdminTaskRecord, newResults.Results.AdminTaskRecord...) 168 size = len(newResults.Results.AdminTaskRecord) 169 case types.QtNetworkPool: 170 cumulativeResults.Results.NetworkPoolRecord = append(cumulativeResults.Results.NetworkPoolRecord, newResults.Results.NetworkPoolRecord...) 171 size = len(newResults.Results.NetworkPoolRecord) 172 case types.QtProviderVdcStorageProfile: 173 cumulativeResults.Results.ProviderVdcStorageProfileRecord = append(cumulativeResults.Results.ProviderVdcStorageProfileRecord, newResults.Results.ProviderVdcStorageProfileRecord...) 174 size = len(newResults.Results.ProviderVdcStorageProfileRecord) 175 case types.QtResourcePool: 176 cumulativeResults.Results.ResourcePoolRecord = append(cumulativeResults.Results.ResourcePoolRecord, newResults.Results.ResourcePoolRecord...) 177 size = len(newResults.Results.ResourcePoolRecord) 178 case types.QtVappNetwork: 179 cumulativeResults.Results.VappNetworkRecord = append(cumulativeResults.Results.VappNetworkRecord, newResults.Results.VappNetworkRecord...) 180 size = len(newResults.Results.VappNetworkRecord) 181 case types.QtAdminVappNetwork: 182 cumulativeResults.Results.AdminVappNetworkRecord = append(cumulativeResults.Results.AdminVappNetworkRecord, newResults.Results.AdminVappNetworkRecord...) 183 size = len(newResults.Results.AdminVappNetworkRecord) 184 185 default: 186 return Results{}, 0, fmt.Errorf("query type %s not supported", queryType) 187 } 188 189 return cumulativeResults, size, nil 190 } 191 192 // cumulativeQuery runs a paginated query and collects all elements until the total number of records is retrieved 193 func (client *Client) cumulativeQuery(queryType string, params, notEncodedParams map[string]string) (Results, error) { 194 return client.cumulativeQueryWithHeaders(queryType, params, notEncodedParams, nil) 195 } 196 197 // cumulativeQueryWithHeaders is the same as cumulativeQuery() but let you add headers to the query 198 func (client *Client) cumulativeQueryWithHeaders(queryType string, params, notEncodedParams map[string]string, headers map[string]string) (Results, error) { 199 var supportedQueryTypes = []string{ 200 types.QtVappTemplate, 201 types.QtAdminVappTemplate, 202 types.QtEdgeGateway, 203 types.QtOrgVdcNetwork, 204 types.QtCatalog, 205 types.QtAdminCatalog, 206 types.QtMedia, 207 types.QtAdminMedia, 208 types.QtCatalogItem, 209 types.QtAdminCatalogItem, 210 types.QtVm, 211 types.QtAdminVm, 212 types.QtVapp, 213 types.QtAdminVapp, 214 types.QtOrgVdc, 215 types.QtAdminOrgVdc, 216 types.QtTask, 217 types.QtAdminTask, 218 types.QtResourcePool, 219 types.QtNetworkPool, 220 types.QtProviderVdcStorageProfile, 221 types.QtVappNetwork, 222 types.QtAdminVappNetwork, 223 } 224 // Make sure the query type is supported 225 // We need to check early, as queries that would return less than 25 items (default page size) would succeed, 226 // but the check on query type will happen once that threshold is crossed. 227 isSupported := false 228 for _, qt := range supportedQueryTypes { 229 if qt == queryType { 230 isSupported = true 231 break 232 } 233 } 234 if !isSupported { 235 return Results{}, fmt.Errorf("[cumulativeQuery] query type %s not supported", queryType) 236 } 237 238 if params == nil { 239 params = make(map[string]string) 240 } 241 if len(notEncodedParams) == 0 { 242 notEncodedParams = map[string]string{"type": queryType} 243 } 244 245 result, err := client.QueryWithNotEncodedParamsWithHeaders(params, notEncodedParams, headers) 246 if err != nil { 247 return Results{}, err 248 } 249 wanted := int(result.Results.Total) 250 retrieved := int(wanted) 251 if retrieved > result.Results.PageSize { 252 retrieved = result.Results.PageSize 253 } 254 if retrieved == wanted { 255 return result, nil 256 } 257 page := result.Results.Page 258 259 var cumulativeResult = Results{ 260 Results: result.Results, 261 client: nil, 262 } 263 264 for retrieved != wanted { 265 page++ 266 notEncodedParams["page"] = fmt.Sprintf("%d", page) 267 var size int 268 newResult, err := client.QueryWithNotEncodedParamsWithHeaders(params, notEncodedParams, headers) 269 if err != nil { 270 return Results{}, err 271 } 272 cumulativeResult, size, err = addResults(queryType, cumulativeResult, newResult) 273 if err != nil { 274 return Results{}, err 275 } 276 retrieved += size 277 } 278 279 return result, nil 280 } 281 282 // queryWithMetadataFields is a wrapper around QueryWithNotEncodedParams with additional metadata fields 283 // being returned. 284 // 285 // * queryType is the type of the query. Only the ones listed within queryFieldsOnDemand are supported 286 // * params and notEncodedParams are the same ones passed to QueryWithNotEncodedParams 287 // * metadataFields is the list of fields to be included in the query results 288 // * if isSystem is true, metadata fields are requested as 'metadata@SYSTEM:fieldName' 289 func (client *Client) queryWithMetadataFields(queryType string, params, notEncodedParams map[string]string, 290 metadataFields []string, isSystem bool) (Results, error) { 291 if notEncodedParams == nil { 292 notEncodedParams = make(map[string]string) 293 } 294 notEncodedParams["type"] = queryType 295 296 if len(metadataFields) == 0 { 297 return client.cumulativeQuery(queryType, params, notEncodedParams) 298 } 299 300 fields, err := queryFieldsOnDemand(queryType) 301 if err != nil { 302 return Results{}, fmt.Errorf("[queryWithMetadataFields] %s", err) 303 } 304 305 if len(fields) == 0 { 306 return Results{}, fmt.Errorf("[queryWithMetadataFields] no fields found for type '%s'", queryType) 307 } 308 metadataFieldText := "" 309 prefix := "metadata" 310 if isSystem { 311 prefix = "metadata@SYSTEM" 312 } 313 for i, field := range metadataFields { 314 metadataFieldText += fmt.Sprintf("%s:%s", prefix, field) 315 if i != len(metadataFields) { 316 metadataFieldText += "," 317 } 318 } 319 320 notEncodedParams["fields"] = strings.Join(fields, ",") + "," + metadataFieldText 321 322 return client.cumulativeQuery(queryType, params, notEncodedParams) 323 } 324 325 // queryByMetadataFilter is a wrapper around QueryWithNotEncodedParams with additional filtering 326 // on metadata fields 327 // Unlike queryWithMetadataFields, this function does not return the metadata fields, but only uses 328 // them to perform the filter. 329 // 330 // * params and notEncodedParams are the same ones passed to QueryWithNotEncodedParams 331 // * metadataFilter is is a map of conditions to use for filtering 332 // * if isSystem is true, metadata fields are requested as 'metadata@SYSTEM:fieldName' 333 func (client *Client) queryByMetadataFilter(queryType string, params, notEncodedParams map[string]string, 334 metadataFilters map[string]MetadataFilter, isSystem bool) (Results, error) { 335 336 if len(metadataFilters) == 0 { 337 return Results{}, fmt.Errorf("[queryByMetadataFilter] no metadata fields provided") 338 } 339 if notEncodedParams == nil { 340 notEncodedParams = make(map[string]string) 341 } 342 notEncodedParams["type"] = queryType 343 344 metadataFilterText := "" 345 prefix := "metadata" 346 if isSystem { 347 prefix = "metadata@SYSTEM" 348 } 349 count := 0 350 for key, value := range metadataFilters { 351 metadataFilterText += fmt.Sprintf("%s:%s==%s:%s", prefix, key, value.Type, url.QueryEscape(value.Value)) 352 if count < len(metadataFilters)-1 { 353 metadataFilterText += ";" 354 } 355 count++ 356 } 357 358 filter, ok := notEncodedParams["filter"] 359 if ok { 360 filter = "(" + filter + ";" + metadataFilterText + ")" 361 } else { 362 filter = metadataFilterText 363 } 364 notEncodedParams["filter"] = filter 365 366 return client.cumulativeQuery(queryType, params, notEncodedParams) 367 }