github.com/vmware/go-vcloud-director/v2@v2.24.0/govcd/network_pool.go (about)

     1  /*
     2   * Copyright 2023 VMware, Inc.  All rights reserved.  Licensed under the Apache v2 License.
     3   */
     4  
     5  package govcd
     6  
     7  import (
     8  	"fmt"
     9  	"github.com/vmware/go-vcloud-director/v2/types/v56"
    10  	"net/url"
    11  	"strings"
    12  )
    13  
    14  type NetworkPool struct {
    15  	NetworkPool *types.NetworkPool
    16  	vcdClient   *VCDClient
    17  }
    18  
    19  var backingUseErrorMessages = map[types.BackingUseConstraint]string{
    20  	types.BackingUseExplicit:       "no element named %s found",
    21  	types.BackingUseWhenOnlyOne:    "no single element found for this backing",
    22  	types.BackingUseFirstAvailable: "no elements found for this backing",
    23  }
    24  
    25  // GetOpenApiUrl retrieves the full URL of a network pool
    26  func (np *NetworkPool) GetOpenApiUrl() (string, error) {
    27  	response, err := url.JoinPath(np.vcdClient.sessionHREF.String(), "admin", "extension", "networkPool", np.NetworkPool.Id)
    28  	if err != nil {
    29  		return "", err
    30  	}
    31  	return response, nil
    32  }
    33  
    34  // GetNetworkPoolSummaries retrieves the list of all available network pools
    35  func (vcdClient *VCDClient) GetNetworkPoolSummaries(queryParameters url.Values) ([]*types.NetworkPool, error) {
    36  	client := vcdClient.Client
    37  	endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointNetworkPoolSummaries
    38  	apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
    39  	if err != nil {
    40  		return nil, err
    41  	}
    42  
    43  	urlRef, err := client.OpenApiBuildEndpoint(endpoint)
    44  	if err != nil {
    45  		return nil, err
    46  	}
    47  	typeResponse := []*types.NetworkPool{{}}
    48  	err = client.OpenApiGetAllItems(apiVersion, urlRef, queryParameters, &typeResponse, nil)
    49  	if err != nil {
    50  		return nil, err
    51  	}
    52  
    53  	return typeResponse, nil
    54  }
    55  
    56  // GetNetworkPoolById retrieves Network Pool with a given ID
    57  func (vcdClient *VCDClient) GetNetworkPoolById(id string) (*NetworkPool, error) {
    58  	if id == "" {
    59  		return nil, fmt.Errorf("network pool lookup requires ID")
    60  	}
    61  
    62  	client := vcdClient.Client
    63  	endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointNetworkPools
    64  	apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
    65  	if err != nil {
    66  		return nil, err
    67  	}
    68  
    69  	urlRef, err := client.OpenApiBuildEndpoint(endpoint, id)
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  
    74  	response := &NetworkPool{
    75  		vcdClient:   vcdClient,
    76  		NetworkPool: &types.NetworkPool{},
    77  	}
    78  
    79  	err = client.OpenApiGetItem(apiVersion, urlRef, nil, response.NetworkPool, nil)
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  
    84  	return response, nil
    85  }
    86  
    87  // GetNetworkPoolByName retrieves a network pool with a given name
    88  // Note. It will return an error if multiple network pools exist with the same name
    89  func (vcdClient *VCDClient) GetNetworkPoolByName(name string) (*NetworkPool, error) {
    90  	if name == "" {
    91  		return nil, fmt.Errorf("network pool lookup requires name")
    92  	}
    93  
    94  	queryParameters := url.Values{}
    95  	queryParameters.Add("filter", "name=="+name)
    96  
    97  	filteredNetworkPools, err := vcdClient.GetNetworkPoolSummaries(queryParameters)
    98  	if err != nil {
    99  		return nil, fmt.Errorf("error getting network pools: %s", err)
   100  	}
   101  
   102  	if len(filteredNetworkPools) == 0 {
   103  		return nil, fmt.Errorf("no network pool found with name '%s' - %s", name, ErrorEntityNotFound)
   104  	}
   105  
   106  	if len(filteredNetworkPools) > 1 {
   107  		return nil, fmt.Errorf("more than one network pool found with name '%s'", name)
   108  	}
   109  
   110  	return vcdClient.GetNetworkPoolById(filteredNetworkPools[0].Id)
   111  }
   112  
   113  // CreateNetworkPool creates a network pool using the given configuration
   114  // It can create any type of network pool
   115  func (vcdClient *VCDClient) CreateNetworkPool(config *types.NetworkPool) (*NetworkPool, error) {
   116  	client := vcdClient.Client
   117  	endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointNetworkPools
   118  	apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  
   123  	urlRef, err := client.OpenApiBuildEndpoint(endpoint)
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  
   128  	result := &NetworkPool{
   129  		NetworkPool: &types.NetworkPool{},
   130  		vcdClient:   vcdClient,
   131  	}
   132  
   133  	err = client.OpenApiPostItem(apiVersion, urlRef, nil, config, result.NetworkPool, nil)
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  
   138  	return result, nil
   139  }
   140  
   141  // Update will change all changeable network pool items
   142  func (np *NetworkPool) Update() error {
   143  	if np == nil || np.NetworkPool == nil || np.NetworkPool.Id == "" {
   144  		return fmt.Errorf("network pool must have ID")
   145  	}
   146  	if np.vcdClient == nil || np.vcdClient.Client.APIVersion == "" {
   147  		return fmt.Errorf("network pool '%s': no client found", np.NetworkPool.Name)
   148  	}
   149  
   150  	client := np.vcdClient.Client
   151  	endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointNetworkPools
   152  	apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
   153  	if err != nil {
   154  		return err
   155  	}
   156  
   157  	urlRef, err := client.OpenApiBuildEndpoint(endpoint, np.NetworkPool.Id)
   158  	if err != nil {
   159  		return err
   160  	}
   161  
   162  	err = client.OpenApiPutItem(apiVersion, urlRef, nil, np.NetworkPool, np.NetworkPool, nil)
   163  	if err != nil {
   164  		return err
   165  	}
   166  
   167  	if err != nil {
   168  		return fmt.Errorf("error updating network pool '%s': %s", np.NetworkPool.Name, err)
   169  	}
   170  
   171  	return nil
   172  }
   173  
   174  // Delete removes a network pool
   175  func (np *NetworkPool) Delete() error {
   176  	if np == nil || np.NetworkPool == nil || np.NetworkPool.Id == "" {
   177  		return fmt.Errorf("network pool must have ID")
   178  	}
   179  	if np.vcdClient == nil || np.vcdClient.Client.APIVersion == "" {
   180  		return fmt.Errorf("network pool '%s': no client found", np.NetworkPool.Name)
   181  	}
   182  
   183  	client := np.vcdClient.Client
   184  	endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointNetworkPools
   185  	apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
   186  	if err != nil {
   187  		return err
   188  	}
   189  
   190  	urlRef, err := client.OpenApiBuildEndpoint(endpoint, np.NetworkPool.Id)
   191  	if err != nil {
   192  		return err
   193  	}
   194  
   195  	err = client.OpenApiDeleteItem(apiVersion, urlRef, nil, nil)
   196  	if err != nil {
   197  		return err
   198  	}
   199  
   200  	if err != nil {
   201  		return fmt.Errorf("error deleting network pool '%s': %s", np.NetworkPool.Name, err)
   202  	}
   203  
   204  	return nil
   205  }
   206  
   207  func backingErrorMessage(constraint types.BackingUseConstraint, name string) string {
   208  	errorMessage := fmt.Sprintf("[constraint: %s] %s", constraint, backingUseErrorMessages[constraint])
   209  	if strings.Contains(errorMessage, "%s") {
   210  		return fmt.Sprintf(errorMessage, name)
   211  	}
   212  	return errorMessage
   213  }
   214  
   215  type getElementFunc[B any] func(*B) string
   216  type validElementFunc[B any] func(*B) bool
   217  
   218  // chooseBackingElement will select a backing element from a list, using the given constraint
   219  // * constraint is the type of choice we are looking for
   220  // * wantedName is the name of the element we want. If we use a constraint other than types.BackingUseExplicit, it can be empty
   221  // * elements is the list of backing elements we want to choose from
   222  // * getEl is a function that, given an element, returns its name
   223  // * validateEl is an optional function that tells whether a given element is valid or not. If missing, we assume all elements are valid
   224  func chooseBackingElement[B any](constraint types.BackingUseConstraint, wantedName string, elements []*B, getEl getElementFunc[B], validateEl validElementFunc[B]) (*B, error) {
   225  	var searchedElement *B
   226  	if validateEl == nil {
   227  		validateEl = func(*B) bool { return true }
   228  	}
   229  	numberOfValidElements := 0
   230  	// We need to pre-calculate the number of valid elements, to use it when constraint == BackingUseWhenOnlyOne
   231  	for _, element := range elements {
   232  		if validateEl(element) {
   233  			numberOfValidElements++
   234  		}
   235  	}
   236  	// availableElements will contain the list of available elements, to be used in error messages
   237  	var availableElements []string
   238  	for _, element := range elements {
   239  		elementName := getEl(element)
   240  		if !validateEl(element) {
   241  			continue
   242  		}
   243  		availableElements = append(availableElements, elementName)
   244  
   245  		switch constraint {
   246  		case types.BackingUseExplicit:
   247  			// When asking for a specific element explicitly, we return it only if the name matches the request)
   248  			if wantedName == elementName {
   249  				searchedElement = element
   250  			}
   251  		case types.BackingUseWhenOnlyOne:
   252  			// With BackingUseWhenOnlyOne, we return the element only if there is a single *valid* element in the list
   253  			if wantedName == "" && numberOfValidElements == 1 {
   254  				searchedElement = element
   255  			}
   256  		case types.BackingUseFirstAvailable:
   257  			// This is the most permissive constraint: we get the first available element
   258  			if wantedName == "" {
   259  				searchedElement = element
   260  			}
   261  		}
   262  		if searchedElement != nil {
   263  			break
   264  		}
   265  	}
   266  	// If no item was retrieved, we build an error message appropriate for the current constraint, and add
   267  	// the list of available elements to it
   268  	if searchedElement == nil {
   269  		return nil, fmt.Errorf(backingErrorMessage(constraint, wantedName)+" - available elements: %v", availableElements)
   270  	}
   271  
   272  	// When we reach this point, we have found what was requested, and return the element
   273  	return searchedElement, nil
   274  }
   275  
   276  // CreateNetworkPoolGeneve creates a network pool of GENEVE type
   277  // The function retrieves the given NSX-T manager and corresponding transport zone names
   278  // If the transport zone name is empty, the first available will be used
   279  func (vcdClient *VCDClient) CreateNetworkPoolGeneve(name, description, nsxtManagerName, transportZoneName string, constraint types.BackingUseConstraint) (*NetworkPool, error) {
   280  	managers, err := vcdClient.QueryNsxtManagerByName(nsxtManagerName)
   281  	if err != nil {
   282  		return nil, err
   283  	}
   284  
   285  	if len(managers) == 0 {
   286  		return nil, fmt.Errorf("no manager '%s' found", nsxtManagerName)
   287  	}
   288  	if len(managers) > 1 {
   289  		return nil, fmt.Errorf("more than one manager '%s' found", nsxtManagerName)
   290  	}
   291  	manager := managers[0]
   292  
   293  	managerId := "urn:vcloud:nsxtmanager:" + extractUuid(managers[0].HREF)
   294  	transportZones, err := vcdClient.GetAllNsxtTransportZones(managerId, nil)
   295  	if err != nil {
   296  		return nil, fmt.Errorf("error retrieving transport zones for manager '%s': %s", manager.Name, err)
   297  	}
   298  	transportZone, err := chooseBackingElement[types.TransportZone](
   299  		constraint,
   300  		transportZoneName,
   301  		transportZones,
   302  		func(tz *types.TransportZone) string { return tz.Name },
   303  		func(tz *types.TransportZone) bool { return !tz.AlreadyImported },
   304  	)
   305  
   306  	if err != nil {
   307  		return nil, err
   308  	}
   309  	if transportZone.AlreadyImported {
   310  		return nil, fmt.Errorf("transport zone '%s' is already imported", transportZone.Name)
   311  	}
   312  
   313  	// Note: in this type of network pool, the managing owner is the NSX-T manager
   314  	managingOwner := types.OpenApiReference{
   315  		Name: manager.Name,
   316  		ID:   managerId,
   317  	}
   318  	var config = &types.NetworkPool{
   319  		Name:             name,
   320  		Description:      description,
   321  		PoolType:         types.NetworkPoolGeneveType,
   322  		ManagingOwnerRef: managingOwner,
   323  		Backing: types.NetworkPoolBacking{
   324  			TransportZoneRef: types.OpenApiReference{
   325  				ID:   transportZone.Id,
   326  				Name: transportZone.Name,
   327  			},
   328  			ProviderRef: managingOwner,
   329  		},
   330  	}
   331  	return vcdClient.CreateNetworkPool(config)
   332  }
   333  
   334  // CreateNetworkPoolPortGroup creates a network pool of PORTGROUP_BACKED type
   335  // The function retrieves the given vCenter and corresponding port group names
   336  // If the port group name is empty, the first available will be used
   337  func (vcdClient *VCDClient) CreateNetworkPoolPortGroup(name, description, vCenterName string, portgroupNames []string, constraint types.BackingUseConstraint) (*NetworkPool, error) {
   338  	vCenter, err := vcdClient.GetVCenterByName(vCenterName)
   339  	if err != nil {
   340  		return nil, fmt.Errorf("error retrieving vCenter '%s': %s", vCenterName, err)
   341  	}
   342  	var params = make(url.Values)
   343  	params.Set("filter", "virtualCenter.id=="+vCenter.VSphereVCenter.VcId)
   344  	portgroups, err := vcdClient.GetAllVcenterImportableDvpgs(params)
   345  	if err != nil {
   346  		return nil, fmt.Errorf("error retrieving portgroups for vCenter '%s': %s", vCenterName, err)
   347  	}
   348  
   349  	var chosenPortgroups []*VcenterImportableDvpg
   350  	var chosenReferences []types.OpenApiReference
   351  	for _, portgroupName := range portgroupNames {
   352  		portGroup, err := chooseBackingElement[VcenterImportableDvpg](
   353  			constraint,
   354  			portgroupName,
   355  			portgroups,
   356  			func(v *VcenterImportableDvpg) string {
   357  				return v.VcenterImportableDvpg.BackingRef.Name
   358  			},
   359  			nil,
   360  		)
   361  
   362  		if err != nil {
   363  			return nil, err
   364  		}
   365  		chosenPortgroups = append(chosenPortgroups, portGroup)
   366  		chosenReferences = append(chosenReferences, types.OpenApiReference{
   367  			Name: portGroup.VcenterImportableDvpg.BackingRef.Name,
   368  			ID:   portGroup.VcenterImportableDvpg.BackingRef.ID,
   369  		})
   370  	}
   371  
   372  	if len(chosenPortgroups) == 0 {
   373  		return nil, fmt.Errorf("no suitable portgroups found for names %v", portgroupNames)
   374  	}
   375  	if len(chosenPortgroups) > 1 {
   376  		if !chosenPortgroups[0].UsableWith(chosenPortgroups...) {
   377  			return nil, fmt.Errorf("portgroups %v should all belong to the same host", portgroupNames)
   378  		}
   379  	}
   380  
   381  	// Note: in this type of network pool, the managing owner is the vCenter
   382  	managingOwner := types.OpenApiReference{
   383  		Name: vCenter.VSphereVCenter.Name,
   384  		ID:   vCenter.VSphereVCenter.VcId,
   385  	}
   386  	config := types.NetworkPool{
   387  		Name:             name,
   388  		Description:      description,
   389  		PoolType:         types.NetworkPoolPortGroupType,
   390  		ManagingOwnerRef: managingOwner,
   391  		Backing: types.NetworkPoolBacking{
   392  			PortGroupRefs: chosenReferences,
   393  			ProviderRef:   managingOwner,
   394  		},
   395  	}
   396  	return vcdClient.CreateNetworkPool(&config)
   397  }
   398  
   399  // CreateNetworkPoolVlan creates a network pool of VLAN type
   400  // The function retrieves the given vCenter and corresponding distributed switch names
   401  // If the distributed switch name is empty, the first available will be used
   402  func (vcdClient *VCDClient) CreateNetworkPoolVlan(name, description, vCenterName, dsName string, ranges []types.VlanIdRange, constraint types.BackingUseConstraint) (*NetworkPool, error) {
   403  	vCenter, err := vcdClient.GetVCenterByName(vCenterName)
   404  	if err != nil {
   405  		return nil, fmt.Errorf("error retrieving vCenter '%s': %s", vCenterName, err)
   406  	}
   407  
   408  	dswitches, err := vcdClient.GetAllVcenterDistributedSwitches(vCenter.VSphereVCenter.VcId, nil)
   409  	if err != nil {
   410  		return nil, fmt.Errorf("error retrieving distributed switches for vCenter '%s': %s", vCenterName, err)
   411  	}
   412  
   413  	dswitch, err := chooseBackingElement[types.VcenterDistributedSwitch](
   414  		constraint,
   415  		dsName,
   416  		dswitches,
   417  		func(t *types.VcenterDistributedSwitch) string { return t.BackingRef.Name },
   418  		nil,
   419  	)
   420  	if err != nil {
   421  		return nil, err
   422  	}
   423  
   424  	// Note: in this type of network pool, the managing owner is the vCenter
   425  	managingOwner := types.OpenApiReference{
   426  		Name: vCenter.VSphereVCenter.Name,
   427  		ID:   vCenter.VSphereVCenter.VcId,
   428  	}
   429  	config := types.NetworkPool{
   430  		Name:             name,
   431  		Description:      description,
   432  		PoolType:         types.NetworkPoolVlanType,
   433  		ManagingOwnerRef: managingOwner,
   434  		Backing: types.NetworkPoolBacking{
   435  			VlanIdRanges: types.VlanIdRanges{
   436  				Values: ranges,
   437  			},
   438  			VdsRefs: []types.OpenApiReference{
   439  				{
   440  					Name: dswitch.BackingRef.Name,
   441  					ID:   dswitch.BackingRef.ID,
   442  				},
   443  			},
   444  			ProviderRef: managingOwner,
   445  		},
   446  	}
   447  	return vcdClient.CreateNetworkPool(&config)
   448  }