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  }