github.com/vmware/go-vcloud-director/v2@v2.24.0/govcd/nsxt_edgegateway.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/netip"
    10  	"net/url"
    11  
    12  	"github.com/vmware/go-vcloud-director/v2/types/v56"
    13  	"github.com/vmware/go-vcloud-director/v2/util"
    14  )
    15  
    16  // NsxtEdgeGateway uses OpenAPI endpoint to operate NSX-T Edge Gateways
    17  type NsxtEdgeGateway struct {
    18  	EdgeGateway *types.OpenAPIEdgeGateway
    19  	client      *Client
    20  }
    21  
    22  // GetNsxtEdgeGatewayById allows retrieving NSX-T edge gateway by ID for Org admins
    23  func (adminOrg *AdminOrg) GetNsxtEdgeGatewayById(id string) (*NsxtEdgeGateway, error) {
    24  	return getNsxtEdgeGatewayById(adminOrg.client, id, nil)
    25  }
    26  
    27  // GetNsxtEdgeGatewayById allows retrieving NSX-T edge gateway by ID for Org users
    28  func (org *Org) GetNsxtEdgeGatewayById(id string) (*NsxtEdgeGateway, error) {
    29  	return getNsxtEdgeGatewayById(org.client, id, nil)
    30  }
    31  
    32  // GetNsxtEdgeGatewayById allows retrieving NSX-T edge gateway by ID for specific VDC
    33  func (vdc *Vdc) GetNsxtEdgeGatewayById(id string) (*NsxtEdgeGateway, error) {
    34  	params := url.Values{}
    35  	filterParams := queryParameterFilterAnd("ownerRef.id=="+vdc.Vdc.ID, params)
    36  	egw, err := getNsxtEdgeGatewayById(vdc.client, id, filterParams)
    37  	if err != nil {
    38  		return nil, err
    39  	}
    40  
    41  	if egw.EdgeGateway.OwnerRef.ID != vdc.Vdc.ID {
    42  		return nil, fmt.Errorf("%s: no NSX-T Edge Gateway with ID '%s' found in VDC '%s'",
    43  			ErrorEntityNotFound, id, vdc.Vdc.ID)
    44  	}
    45  
    46  	return egw, nil
    47  }
    48  
    49  // GetNsxtEdgeGatewayByName allows retrieving NSX-T edge gateway by Name for Org admins
    50  func (adminOrg *AdminOrg) GetNsxtEdgeGatewayByName(name string) (*NsxtEdgeGateway, error) {
    51  	queryParameters := url.Values{}
    52  	queryParameters.Add("filter", "name=="+name)
    53  
    54  	allEdges, err := adminOrg.GetAllNsxtEdgeGateways(queryParameters)
    55  	if err != nil {
    56  		return nil, fmt.Errorf("unable to retrieve Edge Gateway by name '%s': %s", name, err)
    57  	}
    58  
    59  	onlyNsxtEdges := filterOnlyNsxtEdges(allEdges)
    60  
    61  	return returnSingleNsxtEdgeGateway(name, onlyNsxtEdges)
    62  }
    63  
    64  // GetNsxtEdgeGatewayByName allows retrieving NSX-T edge gateway by Name for Org admins
    65  func (org *Org) GetNsxtEdgeGatewayByName(name string) (*NsxtEdgeGateway, error) {
    66  	queryParameters := url.Values{}
    67  	queryParameters.Add("filter", "name=="+name)
    68  
    69  	allEdges, err := org.GetAllNsxtEdgeGateways(queryParameters)
    70  	if err != nil {
    71  		return nil, fmt.Errorf("unable to retrieve Edge Gateway by name '%s': %s", name, err)
    72  	}
    73  
    74  	onlyNsxtEdges := filterOnlyNsxtEdges(allEdges)
    75  
    76  	return returnSingleNsxtEdgeGateway(name, onlyNsxtEdges)
    77  }
    78  
    79  // GetNsxtEdgeGatewayByNameAndOwnerId looks up NSX-T Edge Gateway by name and its owner ID (owner
    80  // can be VDC or VDC Group).
    81  func (org *Org) GetNsxtEdgeGatewayByNameAndOwnerId(edgeGatewayName, ownerId string) (*NsxtEdgeGateway, error) {
    82  	if edgeGatewayName == "" || ownerId == "" {
    83  		return nil, fmt.Errorf("'edgeGatewayName' and 'ownerId' must both be specified")
    84  	}
    85  
    86  	queryParameters := url.Values{}
    87  	queryParameters.Add("filter", fmt.Sprintf("ownerRef.id==%s;name==%s", ownerId, edgeGatewayName))
    88  
    89  	allEdges, err := org.GetAllNsxtEdgeGateways(queryParameters)
    90  	if err != nil {
    91  		return nil, fmt.Errorf("unable to retrieve Edge Gateway by name '%s': %s", edgeGatewayName, err)
    92  	}
    93  
    94  	onlyNsxtEdges := filterOnlyNsxtEdges(allEdges)
    95  
    96  	return returnSingleNsxtEdgeGateway(edgeGatewayName, onlyNsxtEdges)
    97  }
    98  
    99  // GetNsxtEdgeGatewayByName allows to retrieve NSX-T edge gateway by Name for specific VDC
   100  func (vdc *Vdc) GetNsxtEdgeGatewayByName(name string) (*NsxtEdgeGateway, error) {
   101  	queryParameters := url.Values{}
   102  	queryParameters.Add("filter", "name=="+name)
   103  
   104  	allEdges, err := vdc.GetAllNsxtEdgeGateways(queryParameters)
   105  	if err != nil {
   106  		return nil, fmt.Errorf("unable to retrieve Edge Gateway by name '%s': %s", name, err)
   107  	}
   108  
   109  	return returnSingleNsxtEdgeGateway(name, allEdges)
   110  }
   111  
   112  // GetNsxtEdgeGatewayByName allows to retrieve NSX-T edge gateway by Name for specific VDC Group
   113  func (vdcGroup *VdcGroup) GetNsxtEdgeGatewayByName(name string) (*NsxtEdgeGateway, error) {
   114  	if name == "" {
   115  		return nil, fmt.Errorf("'name' must be specified")
   116  	}
   117  
   118  	queryParameters := url.Values{}
   119  	queryParameters.Add("filter", "name=="+name)
   120  
   121  	allEdges, err := vdcGroup.GetAllNsxtEdgeGateways(queryParameters)
   122  	if err != nil {
   123  		return nil, fmt.Errorf("unable to retrieve Edge Gateway by name '%s': %s", name, err)
   124  	}
   125  
   126  	return returnSingleNsxtEdgeGateway(name, allEdges)
   127  }
   128  
   129  // GetAllNsxtEdgeGateways allows to retrieve all NSX-T Edge Gateways
   130  func (vcdClient *VCDClient) GetAllNsxtEdgeGateways(queryParameters url.Values) ([]*NsxtEdgeGateway, error) {
   131  	if vcdClient == nil {
   132  		return nil, fmt.Errorf("vcdClient is empty")
   133  	}
   134  	return getAllNsxtEdgeGateways(&vcdClient.Client, queryParameters)
   135  }
   136  
   137  // GetAllNsxtEdgeGateways allows to retrieve all NSX-T edge gateways for Org Admins
   138  func (adminOrg *AdminOrg) GetAllNsxtEdgeGateways(queryParameters url.Values) ([]*NsxtEdgeGateway, error) {
   139  	return getAllNsxtEdgeGateways(adminOrg.client, queryParameters)
   140  }
   141  
   142  // GetAllNsxtEdgeGateways allows to retrieve all NSX-T edge gateways for Org users
   143  func (org *Org) GetAllNsxtEdgeGateways(queryParameters url.Values) ([]*NsxtEdgeGateway, error) {
   144  	return getAllNsxtEdgeGateways(org.client, queryParameters)
   145  }
   146  
   147  // GetAllNsxtEdgeGateways allows to retrieve all NSX-T edge gateways for specific VDC
   148  func (vdc *Vdc) GetAllNsxtEdgeGateways(queryParameters url.Values) ([]*NsxtEdgeGateway, error) {
   149  	filteredQueryParams := queryParameterFilterAnd("ownerRef.id=="+vdc.Vdc.ID, queryParameters)
   150  	return getAllNsxtEdgeGateways(vdc.client, filteredQueryParams)
   151  }
   152  
   153  // GetAllNsxtEdgeGateways allows to retrieve all NSX-T edge gateways for specific VDC
   154  func (vdcGroup *VdcGroup) GetAllNsxtEdgeGateways(queryParameters url.Values) ([]*NsxtEdgeGateway, error) {
   155  	filteredQueryParams := queryParameterFilterAnd("ownerRef.id=="+vdcGroup.VdcGroup.Id, queryParameters)
   156  	return getAllNsxtEdgeGateways(vdcGroup.client, filteredQueryParams)
   157  }
   158  
   159  // CreateNsxtEdgeGateway allows to create NSX-T edge gateway for Org admins
   160  func (adminOrg *AdminOrg) CreateNsxtEdgeGateway(edgeGatewayConfig *types.OpenAPIEdgeGateway) (*NsxtEdgeGateway, error) {
   161  	if !adminOrg.client.IsSysAdmin {
   162  		return nil, fmt.Errorf("only System Administrator can create Edge Gateway")
   163  	}
   164  
   165  	endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointEdgeGateways
   166  	minimumApiVersion, err := adminOrg.client.getOpenApiHighestElevatedVersion(endpoint)
   167  	if err != nil {
   168  		return nil, err
   169  	}
   170  
   171  	urlRef, err := adminOrg.client.OpenApiBuildEndpoint(endpoint)
   172  	if err != nil {
   173  		return nil, err
   174  	}
   175  
   176  	returnEgw := &NsxtEdgeGateway{
   177  		EdgeGateway: &types.OpenAPIEdgeGateway{},
   178  		client:      adminOrg.client,
   179  	}
   180  
   181  	err = adminOrg.client.OpenApiPostItem(minimumApiVersion, urlRef, nil, edgeGatewayConfig, returnEgw.EdgeGateway, nil)
   182  	if err != nil {
   183  		return nil, fmt.Errorf("error creating Edge Gateway: %s", err)
   184  	}
   185  
   186  	err = returnEgw.reorderUplinks()
   187  	if err != nil {
   188  		return nil, fmt.Errorf("error reordering Edge Gateway Uplinks after update operation: %s", err)
   189  	}
   190  
   191  	return returnEgw, nil
   192  }
   193  
   194  // Refresh reloads NSX-T Edge Gateway contents
   195  func (egw *NsxtEdgeGateway) Refresh() error {
   196  	if egw.EdgeGateway == nil || egw.client == nil || egw.EdgeGateway.ID == "" {
   197  		return fmt.Errorf("cannot refresh Edge Gateway without ID")
   198  	}
   199  
   200  	refreshedEdge, err := getNsxtEdgeGatewayById(egw.client, egw.EdgeGateway.ID, nil)
   201  	if err != nil {
   202  		return fmt.Errorf("error refreshing NSX-T Edge Gateway: %s", err)
   203  	}
   204  	egw.EdgeGateway = refreshedEdge.EdgeGateway
   205  
   206  	err = egw.reorderUplinks()
   207  	if err != nil {
   208  		return fmt.Errorf("error reordering Edge Gateway Uplinks after refresh operation: %s", err)
   209  	}
   210  
   211  	return nil
   212  }
   213  
   214  // Update allows updating NSX-T edge gateway for Org admins
   215  func (egw *NsxtEdgeGateway) Update(edgeGatewayConfig *types.OpenAPIEdgeGateway) (*NsxtEdgeGateway, error) {
   216  	if !egw.client.IsSysAdmin {
   217  		return nil, fmt.Errorf("only System Administrator can update Edge Gateway")
   218  	}
   219  
   220  	endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointEdgeGateways
   221  	apiVersion, err := egw.client.getOpenApiHighestElevatedVersion(endpoint)
   222  	if err != nil {
   223  		return nil, err
   224  	}
   225  
   226  	if edgeGatewayConfig.ID == "" {
   227  		return nil, fmt.Errorf("cannot update Edge Gateway without ID")
   228  	}
   229  
   230  	urlRef, err := egw.client.OpenApiBuildEndpoint(endpoint, edgeGatewayConfig.ID)
   231  	if err != nil {
   232  		return nil, err
   233  	}
   234  
   235  	returnEgw := &NsxtEdgeGateway{
   236  		EdgeGateway: &types.OpenAPIEdgeGateway{},
   237  		client:      egw.client,
   238  	}
   239  
   240  	err = egw.client.OpenApiPutItem(apiVersion, urlRef, nil, edgeGatewayConfig, returnEgw.EdgeGateway, nil)
   241  	if err != nil {
   242  		return nil, fmt.Errorf("error updating Edge Gateway: %s", err)
   243  	}
   244  
   245  	err = egw.reorderUplinks()
   246  	if err != nil {
   247  		return nil, fmt.Errorf("error reordering Edge Gateway Uplinks after update operation: %s", err)
   248  	}
   249  
   250  	return returnEgw, nil
   251  }
   252  
   253  // Delete allows deleting NSX-T edge gateway for sysadmins
   254  func (egw *NsxtEdgeGateway) Delete() error {
   255  	if !egw.client.IsSysAdmin {
   256  		return fmt.Errorf("only Provider can delete Edge Gateway")
   257  	}
   258  
   259  	endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointEdgeGateways
   260  	apiVersion, err := egw.client.getOpenApiHighestElevatedVersion(endpoint)
   261  	if err != nil {
   262  		return err
   263  	}
   264  
   265  	if egw.EdgeGateway.ID == "" {
   266  		return fmt.Errorf("cannot delete Edge Gateway without ID")
   267  	}
   268  
   269  	urlRef, err := egw.client.OpenApiBuildEndpoint(endpoint, egw.EdgeGateway.ID)
   270  	if err != nil {
   271  		return err
   272  	}
   273  
   274  	err = egw.client.OpenApiDeleteItem(apiVersion, urlRef, nil, nil)
   275  
   276  	if err != nil {
   277  		return fmt.Errorf("error deleting Edge Gateway: %s", err)
   278  	}
   279  
   280  	return nil
   281  }
   282  
   283  // MoveToVdcOrVdcGroup moves NSX-T Edge Gateway to another VDC. This can cover such scenarios:
   284  // * Move from VDC to VDC Group
   285  // * Move from VDC Group to VDC (which is part of that VDC Group)
   286  //
   287  // This function is just an Update operation with OwnerRef changed to vdcGroupId, but it is more
   288  // convenient to use it.
   289  // Note. NSX-T Edge Gateway cannot be moved directly from one VDC to another
   290  func (egw *NsxtEdgeGateway) MoveToVdcOrVdcGroup(vdcOrVdcGroupId string) (*NsxtEdgeGateway, error) {
   291  	edgeGatewayConfig := egw.EdgeGateway
   292  	edgeGatewayConfig.OwnerRef = &types.OpenApiReference{ID: vdcOrVdcGroupId}
   293  	// Explicitly unset VDC field because using it fails
   294  	edgeGatewayConfig.OrgVdc = nil
   295  
   296  	return egw.Update(edgeGatewayConfig)
   297  }
   298  
   299  // reorderUplinks will ensure that uplink at slice index 0 is the one backed by NSX-T Tier0 External network.
   300  // NSX-T Edge Gateway can have many uplinks of different types (they are differentiated by 'backingType' field):
   301  // * MANDATORY - exactly 1 uplink to Tier0 Gateway (External network backed by NSX-T T0 Gateway or NSX-T T0 Gateway VRF) [backingType==NSXT_TIER0 or NSXT_VRF_TIER0]
   302  // * OPTIONAL - one or more External Network Uplinks (backed by NSX-T Segment backed External networks) [backingType==IMPORTED_T_LOGICAL_SWITCH]
   303  // It is expected that the Tier0 gateway uplink is always at index 0, but we have seen where VCD API
   304  // shuffles response values therefore it is important to ensure that uplink with
   305  // backingType==NSXT_TIER0 or backingType==NSXT_VRF_TIER0 the element 0 in types.EdgeGatewayUplinks to avoid breaking functionality
   306  // in upstream code.
   307  //
   308  // Note. This function wil be a noop in 10.4.0, because `backingType` was not present. However, this
   309  // poses no risks because the can be only 1 uplink up to 10.4.1, when `backingType` was introduced.
   310  func (egw *NsxtEdgeGateway) reorderUplinks() error {
   311  	if egw == nil || egw.EdgeGateway == nil {
   312  		return fmt.Errorf("edge gateway cannot be nil ")
   313  	}
   314  
   315  	if len(egw.EdgeGateway.EdgeGatewayUplinks) == 0 {
   316  		return fmt.Errorf("no uplinks present in Edge Gateway")
   317  	}
   318  
   319  	egw.EdgeGateway.EdgeGatewayUplinks = reorderEdgeGatewayUplinks(egw.EdgeGateway.EdgeGatewayUplinks)
   320  	return nil
   321  }
   322  
   323  // getNsxtEdgeGatewayById is a private parent for wrapped functions:
   324  // func (adminOrg *AdminOrg) GetNsxtEdgeGatewayByName(id string) (*NsxtEdgeGateway, error)
   325  // func (org *Org) GetNsxtEdgeGatewayByName(id string) (*NsxtEdgeGateway, error)
   326  // func (vdc *Vdc) GetNsxtEdgeGatewayById(id string) (*NsxtEdgeGateway, error)
   327  func getNsxtEdgeGatewayById(client *Client, id string, queryParameters url.Values) (*NsxtEdgeGateway, error) {
   328  	endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointEdgeGateways
   329  	minimumApiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
   330  	if err != nil {
   331  		return nil, err
   332  	}
   333  
   334  	if id == "" {
   335  		return nil, fmt.Errorf("empty Edge Gateway ID")
   336  	}
   337  
   338  	urlRef, err := client.OpenApiBuildEndpoint(endpoint, id)
   339  	if err != nil {
   340  		return nil, err
   341  	}
   342  
   343  	egw := &NsxtEdgeGateway{
   344  		EdgeGateway: &types.OpenAPIEdgeGateway{},
   345  		client:      client,
   346  	}
   347  
   348  	err = client.OpenApiGetItem(minimumApiVersion, urlRef, queryParameters, egw.EdgeGateway, nil)
   349  	if err != nil {
   350  		return nil, err
   351  	}
   352  
   353  	if egw.EdgeGateway.GatewayBacking.GatewayType != "NSXT_BACKED" {
   354  		return nil, fmt.Errorf("%s: this is not NSX-T Edge Gateway (%s)",
   355  			ErrorEntityNotFound, egw.EdgeGateway.GatewayBacking.GatewayType)
   356  	}
   357  
   358  	err = egw.reorderUplinks()
   359  	if err != nil {
   360  		return nil, fmt.Errorf("error reordering Edge Gateway Uplink after API retrieval")
   361  	}
   362  
   363  	return egw, nil
   364  }
   365  
   366  // returnSingleNsxtEdgeGateway helps to reduce code duplication for `GetNsxtEdgeGatewayByName` functions with different
   367  // receivers
   368  func returnSingleNsxtEdgeGateway(name string, allEdges []*NsxtEdgeGateway) (*NsxtEdgeGateway, error) {
   369  	if len(allEdges) > 1 {
   370  		return nil, fmt.Errorf("got more than 1 Edge Gateway by name '%s' %d", name, len(allEdges))
   371  	}
   372  
   373  	if len(allEdges) < 1 {
   374  		return nil, fmt.Errorf("%s: got 0 Edge Gateways by name '%s'", ErrorEntityNotFound, name)
   375  	}
   376  
   377  	return allEdges[0], nil
   378  }
   379  
   380  // getAllNsxtEdgeGateways is a private parent for wrapped functions:
   381  // func (adminOrg *AdminOrg) GetAllNsxtEdgeGateways(queryParameters url.Values) ([]*NsxtEdgeGateway, error)
   382  // func (org *Org) GetAllNsxtEdgeGateways(queryParameters url.Values) ([]*NsxtEdgeGateway, error)
   383  // func (vdc *Vdc) GetAllNsxtEdgeGateways(queryParameters url.Values) ([]*NsxtEdgeGateway, error)
   384  func getAllNsxtEdgeGateways(client *Client, queryParameters url.Values) ([]*NsxtEdgeGateway, error) {
   385  	endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointEdgeGateways
   386  	minimumApiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
   387  	if err != nil {
   388  		return nil, err
   389  	}
   390  
   391  	urlRef, err := client.OpenApiBuildEndpoint(endpoint)
   392  	if err != nil {
   393  		return nil, err
   394  	}
   395  
   396  	typeResponses := []*types.OpenAPIEdgeGateway{{}}
   397  	err = client.OpenApiGetAllItems(minimumApiVersion, urlRef, queryParameters, &typeResponses, nil)
   398  	if err != nil {
   399  		return nil, err
   400  	}
   401  
   402  	// Wrap all typeResponses into NsxtEdgeGateway types with client
   403  	wrappedResponses := make([]*NsxtEdgeGateway, len(typeResponses))
   404  	for sliceIndex := range typeResponses {
   405  		wrappedResponses[sliceIndex] = &NsxtEdgeGateway{
   406  			EdgeGateway: typeResponses[sliceIndex],
   407  			client:      client,
   408  		}
   409  	}
   410  
   411  	onlyNsxtEdges := filterOnlyNsxtEdges(wrappedResponses)
   412  
   413  	// Reorder uplink in all Edge Gateways
   414  	for edgeIndex := range onlyNsxtEdges {
   415  		err := onlyNsxtEdges[edgeIndex].reorderUplinks()
   416  		if err != nil {
   417  			return nil, fmt.Errorf("error reordering NSX-T Edge Gateway Uplinks for gateway '%s' ('%s'): %s",
   418  				onlyNsxtEdges[edgeIndex].EdgeGateway.Name, onlyNsxtEdges[edgeIndex].EdgeGateway.ID, err)
   419  		}
   420  	}
   421  
   422  	return onlyNsxtEdges, nil
   423  }
   424  
   425  // filterOnlyNsxtEdges filters our list of edge gateways only for NSXT_BACKED ones because original endpoint can
   426  // return NSX-V and NSX-T backed edge gateways.
   427  func filterOnlyNsxtEdges(allEdges []*NsxtEdgeGateway) []*NsxtEdgeGateway {
   428  	filteredEdges := make([]*NsxtEdgeGateway, 0)
   429  
   430  	for index := range allEdges {
   431  		if allEdges[index] != nil && allEdges[index].EdgeGateway != nil &&
   432  			allEdges[index].EdgeGateway.GatewayBacking != nil &&
   433  			allEdges[index].EdgeGateway.GatewayBacking.GatewayType == "NSXT_BACKED" {
   434  			filteredEdges = append(filteredEdges, allEdges[index])
   435  		}
   436  	}
   437  
   438  	return filteredEdges
   439  }
   440  
   441  // GetUsedIpAddresses uses dedicated endpoint to retrieve used IP addresses in an Edge Gateway
   442  func (egw *NsxtEdgeGateway) GetUsedIpAddresses(queryParameters url.Values) ([]*types.GatewayUsedIpAddress, error) {
   443  	if egw.EdgeGateway == nil || egw.EdgeGateway.ID == "" {
   444  		return nil, fmt.Errorf("edge gateway ID must be set to retrieve used IP addresses")
   445  	}
   446  	client := egw.client
   447  
   448  	endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointEdgeGatewayUsedIpAddresses
   449  	apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
   450  	if err != nil {
   451  		return nil, err
   452  	}
   453  
   454  	urlRef, err := client.OpenApiBuildEndpoint(fmt.Sprintf(endpoint, egw.EdgeGateway.ID))
   455  	if err != nil {
   456  		return nil, err
   457  	}
   458  
   459  	typeResponse := make([]*types.GatewayUsedIpAddress, 0)
   460  	err = client.OpenApiGetAllItems(apiVersion, urlRef, queryParameters, &typeResponse, nil)
   461  	if err != nil {
   462  		return nil, err
   463  	}
   464  
   465  	return typeResponse, nil
   466  }
   467  
   468  // GetUnusedExternalIPAddresses will retrieve a requiredIpCount of unused IP addresses for Edge
   469  // Gateway
   470  // Arguments:
   471  // * `requiredIpCount` (how many unuseds IPs should be returned). It will fail and return an
   472  // error if all IPs specified in 'requiredIpCount' cannot be found.
   473  // * `optionalSubnet` is specified (CIDR notation, e.g. 192.168.1.0/24), it will look for an IP in
   474  // this subnet only.
   475  // * `refresh` defines if Edge Gateway structure should be retrieved with latest data before
   476  // performing IP lookup operation
   477  //
   478  // Input and return arguments are using Go's native 'netip' package for IP addressing. This ensures
   479  // correct support for IPv4 and IPv6 IPs.
   480  // `netip.ParseAddr`, `netip.ParsePrefix`, `netip.Addr.String` functions can be used for conversion
   481  // from/to strings
   482  //
   483  // This function performs below listed steps:
   484  // 1. Retrieves a complete list of IPs in Edge Gateway uplinks (returns error if none are found)
   485  // 2. if 'optionalSubnet' was specified - filter IP addresses to only fall into that subnet
   486  // 3. Retrieves all used IP addresses in Edge Gateway using dedicated API endpoint
   487  // 4. Subtracts used IP addresses from available list of IPs in uplink (optionally filtered by optionalSubnet in step 2)
   488  // 5. Checks if 'requiredIpCount' criteria is met, returns error otherwise
   489  // 6. Returns required amount of unused IPs (as defined in 'requiredIpCount')
   490  //
   491  // Notes:
   492  // * This function uses Go's builtin `netip` package to avoid any string processing of IPs and
   493  // supports IPv4 and IPv6 addressing.
   494  // * If an unused IP is not found it will return 'netip.Addr{}' (not using *netip.Addr{} to match
   495  // library semantics) and an error
   496  // * It will return an error if any of uplink IP ranges End IP address is lower than Start IP
   497  // address
   498  func (egw *NsxtEdgeGateway) GetUnusedExternalIPAddresses(requiredIpCount int, optionalSubnet netip.Prefix, refresh bool) ([]netip.Addr, error) {
   499  	if refresh {
   500  		err := egw.Refresh()
   501  		if err != nil {
   502  			return nil, fmt.Errorf("error refreshing Edge Gateway: %s", err)
   503  		}
   504  	}
   505  	usedIpAddresses, err := egw.GetUsedIpAddresses(nil)
   506  	if err != nil {
   507  		return nil, fmt.Errorf("error getting used IP addresses for Edge Gateway: %s", err)
   508  	}
   509  
   510  	return getUnusedExternalIPAddress(egw.EdgeGateway.EdgeGatewayUplinks, usedIpAddresses, requiredIpCount, optionalSubnet)
   511  }
   512  
   513  // GetAllUnusedExternalIPAddresses will retrieve all unassigned IP addresses for Edge Gateway It is
   514  // similar to GetUnusedExternalIPAddresses but returns all unused IPs instead of a specific amount
   515  func (egw *NsxtEdgeGateway) GetAllUnusedExternalIPAddresses(refresh bool) ([]netip.Addr, error) {
   516  	if refresh {
   517  		err := egw.Refresh()
   518  		if err != nil {
   519  			return nil, fmt.Errorf("error refreshing Edge Gateway: %s", err)
   520  		}
   521  	}
   522  	usedIpAddresses, err := egw.GetUsedIpAddresses(nil)
   523  	if err != nil {
   524  		return nil, fmt.Errorf("error getting used IP addresses for Edge Gateway: %s", err)
   525  	}
   526  
   527  	return getAllUnusedExternalIPAddresses(egw.EdgeGateway.EdgeGatewayUplinks, usedIpAddresses, netip.Prefix{})
   528  }
   529  
   530  // GetAllocatedIpCount traverses all subnets in Edge Gateway and returns a count of allocated IP
   531  // count for each subnet in each uplink
   532  func (egw *NsxtEdgeGateway) GetAllocatedIpCount(refresh bool) (int, error) {
   533  	if refresh {
   534  		err := egw.Refresh()
   535  		if err != nil {
   536  			return 0, fmt.Errorf("error refreshing Edge Gateway: %s", err)
   537  		}
   538  	}
   539  
   540  	allocatedIpCount := 0
   541  
   542  	for _, uplink := range egw.EdgeGateway.EdgeGatewayUplinks {
   543  		for _, subnet := range uplink.Subnets.Values {
   544  			if subnet.TotalIPCount != nil {
   545  				allocatedIpCount += *subnet.TotalIPCount
   546  			}
   547  		}
   548  	}
   549  
   550  	return allocatedIpCount, nil
   551  }
   552  
   553  // GetPrimaryNetworkAllocatedIpCount returns total count of allocated IPs for first NSX-T Edge
   554  // Gateway uplink
   555  func (egw *NsxtEdgeGateway) GetPrimaryNetworkAllocatedIpCount(refresh bool) (int, error) {
   556  	if refresh {
   557  		err := egw.Refresh()
   558  		if err != nil {
   559  			return 0, fmt.Errorf("error refreshing Edge Gateway: %s", err)
   560  		}
   561  	}
   562  
   563  	allocatedIpCount := 0
   564  
   565  	for _, subnet := range egw.EdgeGateway.EdgeGatewayUplinks[0].Subnets.Values {
   566  		if subnet.TotalIPCount != nil {
   567  			allocatedIpCount += *subnet.TotalIPCount
   568  		}
   569  	}
   570  
   571  	return allocatedIpCount, nil
   572  }
   573  
   574  // GetAllocatedIpCountByUplinkType will return a sum of allocated IPs for particular `uplinkType`
   575  // `uplinkType` can be one of 'NSXT_TIER0', 'NSXT_VRF_TIER0', 'IMPORTED_T_LOGICAL_SWITCH'
   576  //
   577  // Note. This function is based on BackingType field and requires at least VCD 10.4.1
   578  func (egw *NsxtEdgeGateway) GetAllocatedIpCountByUplinkType(refresh bool, uplinkType string) (int, error) {
   579  	if egw.client.APIVCDMaxVersionIs("< 37.1") {
   580  		return 0, fmt.Errorf("this function requires at least VCD 10.4.1 to work")
   581  	}
   582  
   583  	if uplinkType != "NSXT_TIER0" &&
   584  		uplinkType != "IMPORTED_T_LOGICAL_SWITCH" &&
   585  		uplinkType != "NSXT_VRF_TIER0" {
   586  		return 0, fmt.Errorf("invalid 'uplinkType', expected 'NSXT_TIER0', 'IMPORTED_T_LOGICAL_SWITCH' or 'NSXT_VRF_TIER0', got: %s", uplinkType)
   587  	}
   588  
   589  	if refresh {
   590  		err := egw.Refresh()
   591  		if err != nil {
   592  			return 0, fmt.Errorf("error refreshing Edge Gateway: %s", err)
   593  		}
   594  	}
   595  
   596  	allocatedIpCount := 0
   597  
   598  	for _, uplink := range egw.EdgeGateway.EdgeGatewayUplinks {
   599  		// counting IPs only for specific uplink type
   600  		if uplink.BackingType != nil && *uplink.BackingType != uplinkType {
   601  			continue
   602  		}
   603  		for _, subnet := range uplink.Subnets.Values {
   604  			if subnet.TotalIPCount != nil {
   605  				allocatedIpCount += *subnet.TotalIPCount
   606  			}
   607  		}
   608  	}
   609  
   610  	return allocatedIpCount, nil
   611  }
   612  
   613  // GetUsedIpAddressSlice retrieves a list of used IP addresses in an Edge Gateway and returns it
   614  // using native Go type '[]netip.Addr'
   615  func (egw *NsxtEdgeGateway) GetUsedIpAddressSlice(refresh bool) ([]netip.Addr, error) {
   616  	if refresh {
   617  		err := egw.Refresh()
   618  		if err != nil {
   619  			return nil, fmt.Errorf("error refreshing Edge Gateway: %s", err)
   620  		}
   621  	}
   622  	usedIpAddresses, err := egw.GetUsedIpAddresses(nil)
   623  	if err != nil {
   624  		return nil, fmt.Errorf("error getting used IP addresses for Edge Gateway: %s", err)
   625  	}
   626  
   627  	return flattenGatewayUsedIpAddressesToIpSlice(usedIpAddresses)
   628  }
   629  
   630  // QuickDeallocateIpCount refreshes Edge Gateway structure and deallocates specified ipCount from it
   631  // by modifying Uplink structure and calling Update() on it.
   632  //
   633  // Notes:
   634  // * This is a reverse operation to QuickAllocateIpCount and is provided for convenience as the API
   635  // does not support negative values for QuickAddAllocatedIPCount field
   636  // * This function modifies Edge Gateway structure and calls update. To only modify structure,
   637  // please use `NsxtEdgeGateway.DeallocateIpCount` function
   638  func (egw *NsxtEdgeGateway) QuickDeallocateIpCount(ipCount int) (*NsxtEdgeGateway, error) {
   639  	if egw.EdgeGateway == nil {
   640  		return nil, fmt.Errorf("edge gateway is not initialized")
   641  	}
   642  
   643  	err := egw.Refresh()
   644  	if err != nil {
   645  		return nil, fmt.Errorf("error refreshing Edge Gateway: %s", err)
   646  	}
   647  
   648  	err = egw.DeallocateIpCount(ipCount)
   649  	if err != nil {
   650  		return nil, fmt.Errorf("error deallocating IP count: %s", err)
   651  	}
   652  
   653  	return egw.Update(egw.EdgeGateway)
   654  }
   655  
   656  // DeallocateIpCount modifies the structure to deallocate IP addresses from the Edge Gateway
   657  // uplinks.
   658  //
   659  // Notes:
   660  // * This function does not call Update() on the Edge Gateway and it is up to the caller to perform
   661  // this operation (or use NsxtEdgeGateway.QuickDeallocateIpCount which wraps this function and
   662  // performs API call)
   663  // * Use `QuickAddAllocatedIPCount` field in the uplink structure to leverage VCD API directly for
   664  // allocating IP addresses.
   665  func (egw *NsxtEdgeGateway) DeallocateIpCount(deallocateIpCount int) error {
   666  	if deallocateIpCount < 0 {
   667  		return fmt.Errorf("deallocateIpCount must be greater than 0")
   668  	}
   669  
   670  	if egw == nil || egw.EdgeGateway == nil {
   671  		return fmt.Errorf("edge gateway structure cannot be nil")
   672  	}
   673  
   674  	edgeGatewayType := egw.EdgeGateway
   675  
   676  	for uplinkIndex, uplink := range edgeGatewayType.EdgeGatewayUplinks {
   677  		for subnetIndex, subnet := range uplink.Subnets.Values {
   678  
   679  			// TotalIPCount is an address of a variable so it needs to be dereferenced for easier arithmetic
   680  			// operations. In the end of processing the value is set back to the original location.
   681  			singleSubnetTotalIpCount := *edgeGatewayType.EdgeGatewayUplinks[uplinkIndex].Subnets.Values[subnetIndex].TotalIPCount
   682  
   683  			if singleSubnetTotalIpCount > 0 {
   684  				util.Logger.Printf("[DEBUG] Edge Gateway deallocating IPs from subnet '%s', TotalIPCount '%d', deallocate IP count '%d'",
   685  					subnet.Gateway, subnet.TotalIPCount, deallocateIpCount)
   686  
   687  				// If a subnet contains more allocated IPs than we need to deallocate - deallocate only what we need
   688  				if singleSubnetTotalIpCount >= deallocateIpCount {
   689  					singleSubnetTotalIpCount -= deallocateIpCount
   690  
   691  					// To make deallocation work one must set this to true
   692  					edgeGatewayType.EdgeGatewayUplinks[uplinkIndex].Subnets.Values[subnetIndex].AutoAllocateIPRanges = true
   693  
   694  					deallocateIpCount = 0
   695  				} else { // If we have less IPs allocated than we need to deallocate - deallocate all of them
   696  					deallocateIpCount -= singleSubnetTotalIpCount
   697  					singleSubnetTotalIpCount = 0
   698  					edgeGatewayType.EdgeGatewayUplinks[uplinkIndex].Subnets.Values[subnetIndex].AutoAllocateIPRanges = true // To make deallocation work one must set this to true
   699  					util.Logger.Printf("[DEBUG] Edge Gateway IP count after partial deallocation %d", edgeGatewayType.EdgeGatewayUplinks[uplinkIndex].Subnets.Values[subnetIndex].TotalIPCount)
   700  				}
   701  			}
   702  
   703  			// Setting value back to original location after all operations
   704  			edgeGatewayType.EdgeGatewayUplinks[uplinkIndex].Subnets.Values[subnetIndex].TotalIPCount = &singleSubnetTotalIpCount
   705  			util.Logger.Printf("[DEBUG] Edge Gateway IP count after complete deallocation %d", edgeGatewayType.EdgeGatewayUplinks[uplinkIndex].Subnets.Values[subnetIndex].TotalIPCount)
   706  
   707  			if deallocateIpCount == 0 {
   708  				break
   709  			}
   710  		}
   711  	}
   712  
   713  	if deallocateIpCount > 0 {
   714  		return fmt.Errorf("not enough IPs allocated to deallocate requested '%d' IPs", deallocateIpCount)
   715  	}
   716  
   717  	return nil
   718  }
   719  
   720  // GetQoS retrieves QoS (rate limiting) configuration for an NSX-T Edge Gateway
   721  func (egw *NsxtEdgeGateway) GetQoS() (*types.NsxtEdgeGatewayQos, error) {
   722  	if egw.EdgeGateway == nil || egw.client == nil || egw.EdgeGateway.ID == "" {
   723  		return nil, fmt.Errorf("cannot get QoS for NSX-T Edge Gateway without ID")
   724  	}
   725  
   726  	client := egw.client
   727  	endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointEdgeGatewayQos
   728  	apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
   729  	if err != nil {
   730  		return nil, err
   731  	}
   732  
   733  	urlRef, err := client.OpenApiBuildEndpoint(fmt.Sprintf(endpoint, egw.EdgeGateway.ID))
   734  	if err != nil {
   735  		return nil, err
   736  	}
   737  
   738  	qos := &types.NsxtEdgeGatewayQos{}
   739  	err = client.OpenApiGetItem(apiVersion, urlRef, nil, qos, nil)
   740  	if err != nil {
   741  		return nil, err
   742  	}
   743  
   744  	return qos, nil
   745  }
   746  
   747  // UpdateQoS updates QoS (rate limiting) configuration for an NSX-T Edge Gateway
   748  func (egw *NsxtEdgeGateway) UpdateQoS(qosConfig *types.NsxtEdgeGatewayQos) (*types.NsxtEdgeGatewayQos, error) {
   749  	if egw.EdgeGateway == nil || egw.client == nil || egw.EdgeGateway.ID == "" {
   750  		return nil, fmt.Errorf("cannot update QoS for NSX-T Edge Gateway without ID")
   751  	}
   752  
   753  	client := egw.client
   754  	endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointEdgeGatewayQos
   755  	apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
   756  	if err != nil {
   757  		return nil, err
   758  	}
   759  
   760  	urlRef, err := client.OpenApiBuildEndpoint(fmt.Sprintf(endpoint, egw.EdgeGateway.ID))
   761  	if err != nil {
   762  		return nil, err
   763  	}
   764  
   765  	// update QoS with given qosConfig
   766  	updatedQos := &types.NsxtEdgeGatewayQos{}
   767  	err = client.OpenApiPutItem(apiVersion, urlRef, nil, qosConfig, updatedQos, nil)
   768  	if err != nil {
   769  		return nil, err
   770  	}
   771  
   772  	return updatedQos, nil
   773  }
   774  
   775  // GetDhcpForwarder gets DHCP forwarder configuration for an NSX-T Edge Gateway
   776  func (egw *NsxtEdgeGateway) GetDhcpForwarder() (*types.NsxtEdgeGatewayDhcpForwarder, error) {
   777  	if egw.EdgeGateway == nil || egw.client == nil || egw.EdgeGateway.ID == "" {
   778  		return nil, fmt.Errorf("cannot get DHCP forwarder for NSX-T Edge Gateway without ID")
   779  	}
   780  
   781  	client := egw.client
   782  	endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointEdgeGatewayDhcpForwarder
   783  	apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
   784  	if err != nil {
   785  		return nil, err
   786  	}
   787  
   788  	urlRef, err := client.OpenApiBuildEndpoint(fmt.Sprintf(endpoint, egw.EdgeGateway.ID))
   789  	if err != nil {
   790  		return nil, err
   791  	}
   792  
   793  	dhcpForwarder := &types.NsxtEdgeGatewayDhcpForwarder{}
   794  	err = client.OpenApiGetItem(apiVersion, urlRef, nil, dhcpForwarder, nil)
   795  	if err != nil {
   796  		return nil, err
   797  	}
   798  
   799  	return dhcpForwarder, nil
   800  }
   801  
   802  // UpdateDhcpForwarder updates DHCP forwarder configuration for an NSX-T Edge Gateway
   803  func (egw *NsxtEdgeGateway) UpdateDhcpForwarder(dhcpForwarderConfig *types.NsxtEdgeGatewayDhcpForwarder) (*types.NsxtEdgeGatewayDhcpForwarder, error) {
   804  	if egw.EdgeGateway == nil || egw.client == nil || egw.EdgeGateway.ID == "" {
   805  		return nil, fmt.Errorf("cannot update DHCP forwarder for NSX-T Edge Gateway without ID")
   806  	}
   807  
   808  	client := egw.client
   809  	endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointEdgeGatewayDhcpForwarder
   810  	apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
   811  	if err != nil {
   812  		return nil, err
   813  	}
   814  
   815  	urlRef, err := client.OpenApiBuildEndpoint(fmt.Sprintf(endpoint, egw.EdgeGateway.ID))
   816  	if err != nil {
   817  		return nil, err
   818  	}
   819  
   820  	// update DHCP forwarder with given dhcpForwarderConfig
   821  	updatedDhcpForwarder, err := egw.GetDhcpForwarder()
   822  	if err != nil {
   823  		return nil, err
   824  	}
   825  	dhcpForwarderConfig.Version = updatedDhcpForwarder.Version
   826  
   827  	err = client.OpenApiPutItem(apiVersion, urlRef, nil, dhcpForwarderConfig, updatedDhcpForwarder, nil)
   828  	if err != nil {
   829  		return nil, err
   830  	}
   831  
   832  	return updatedDhcpForwarder, nil
   833  }
   834  
   835  // GetSlaacProfile gets SLAAC (Stateless Address Autoconfiguration) Profile configuration for an
   836  // NSX-T Edge Gateway.
   837  // Note. It represents DHCPv6 Edge Gateway configuration in UI
   838  func (egw *NsxtEdgeGateway) GetSlaacProfile() (*types.NsxtEdgeGatewaySlaacProfile, error) {
   839  	if egw.EdgeGateway == nil || egw.client == nil || egw.EdgeGateway.ID == "" {
   840  		return nil, fmt.Errorf("cannot get SLAAC Profile for NSX-T Edge Gateway without ID")
   841  	}
   842  
   843  	client := egw.client
   844  	endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointEdgeGatewaySlaacProfile
   845  	apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
   846  	if err != nil {
   847  		return nil, err
   848  	}
   849  
   850  	urlRef, err := client.OpenApiBuildEndpoint(fmt.Sprintf(endpoint, egw.EdgeGateway.ID))
   851  	if err != nil {
   852  		return nil, err
   853  	}
   854  
   855  	slaacProfile := &types.NsxtEdgeGatewaySlaacProfile{}
   856  	err = client.OpenApiGetItem(apiVersion, urlRef, nil, slaacProfile, nil)
   857  	if err != nil {
   858  		return nil, err
   859  	}
   860  
   861  	return slaacProfile, nil
   862  }
   863  
   864  // UpdateSlaacProfile creates a SLAAC (Stateless Address Autoconfiguration) profile or updates the
   865  // existing one if it already exists.
   866  // Note. It represents DHCPv6 Edge Gateway configuration in UI
   867  func (egw *NsxtEdgeGateway) UpdateSlaacProfile(slaacProfileConfig *types.NsxtEdgeGatewaySlaacProfile) (*types.NsxtEdgeGatewaySlaacProfile, error) {
   868  	if egw.EdgeGateway == nil || egw.client == nil || egw.EdgeGateway.ID == "" {
   869  		return nil, fmt.Errorf("cannot update SLAAC Profile for NSX-T Edge Gateway without ID")
   870  	}
   871  
   872  	client := egw.client
   873  	endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointEdgeGatewaySlaacProfile
   874  	apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
   875  	if err != nil {
   876  		return nil, err
   877  	}
   878  
   879  	urlRef, err := client.OpenApiBuildEndpoint(fmt.Sprintf(endpoint, egw.EdgeGateway.ID))
   880  	if err != nil {
   881  		return nil, err
   882  	}
   883  
   884  	updatedSlaacProfile := &types.NsxtEdgeGatewaySlaacProfile{}
   885  	err = client.OpenApiPutItem(apiVersion, urlRef, nil, slaacProfileConfig, updatedSlaacProfile, nil)
   886  	if err != nil {
   887  		return nil, err
   888  	}
   889  
   890  	return updatedSlaacProfile, nil
   891  }
   892  
   893  func getAllUnusedExternalIPAddresses(uplinks []types.EdgeGatewayUplinks, usedIpAddresses []*types.GatewayUsedIpAddress, optionalSubnet netip.Prefix) ([]netip.Addr, error) {
   894  	// 1. Flatten all IP ranges in Edge Gateway using Go's native 'netip.Addr' IP container instead
   895  	// of plain strings because it is more robust (supports IPv4 and IPv6 and also comparison
   896  	// operator)
   897  	assignedIpSlice, err := flattenEdgeGatewayUplinkToIpSlice(uplinks)
   898  	if err != nil {
   899  		return nil, fmt.Errorf("error listing all IPs in Edge Gateway: %s", err)
   900  	}
   901  
   902  	if len(assignedIpSlice) == 0 {
   903  		return nil, fmt.Errorf("no IPs found in Edge Gateway configuration")
   904  	}
   905  
   906  	// 2. Optionally filter given IP ranges by optionalSubnet value (if specified)
   907  	if optionalSubnet != (netip.Prefix{}) {
   908  		assignedIpSlice, err = filterIpSlicesBySubnet(assignedIpSlice, optionalSubnet)
   909  		if err != nil {
   910  			return nil, fmt.Errorf("error filtering ranges for given subnet '%s': %s", optionalSubnet, err)
   911  		}
   912  	}
   913  
   914  	// 3. Get Used IP addresses in Edge Gateway in the same slice format
   915  	usedIpSlice, err := flattenGatewayUsedIpAddressesToIpSlice(usedIpAddresses)
   916  	if err != nil {
   917  		return nil, fmt.Errorf("could not flatten Edge Gateway used IP addresses: %s", err)
   918  	}
   919  
   920  	// 4. Get all unused IPs
   921  	// (allIPs - allUsedIPs) = allUnusedIPs
   922  	unusedIps := ipSliceDifference(assignedIpSlice, usedIpSlice)
   923  
   924  	return unusedIps, nil
   925  }
   926  
   927  func getUnusedExternalIPAddress(uplinks []types.EdgeGatewayUplinks, usedIpAddresses []*types.GatewayUsedIpAddress, requiredIpCount int, optionalSubnet netip.Prefix) ([]netip.Addr, error) {
   928  	unusedIps, err := getAllUnusedExternalIPAddresses(uplinks, usedIpAddresses, optionalSubnet)
   929  	if err != nil {
   930  		return nil, fmt.Errorf("error getting all unused IPs: %s", err)
   931  	}
   932  
   933  	// 5. Check if 'requiredIpCount' criteria is met
   934  	if len(unusedIps) < requiredIpCount {
   935  		return nil, fmt.Errorf("not enough unused IPs found. Expected %d, got %d", requiredIpCount, len(unusedIps))
   936  	}
   937  
   938  	// 6. Return required amount of unused IPs
   939  	return unusedIps[:requiredIpCount], nil
   940  }
   941  
   942  // flattenEdgeGatewayUplinkToIpSlice processes Edge Gateway Uplink structure and creates a slice of
   943  // all available IPs
   944  func flattenEdgeGatewayUplinkToIpSlice(uplinks []types.EdgeGatewayUplinks) ([]netip.Addr, error) {
   945  	assignedIpSlice := make([]netip.Addr, 0)
   946  
   947  	for _, edgeGatewayUplink := range uplinks {
   948  		for _, edgeGatewayUplinkSubnet := range edgeGatewayUplink.Subnets.Values {
   949  			for _, r := range edgeGatewayUplinkSubnet.IPRanges.Values {
   950  				// Convert IPs to netip.Addr
   951  				startIp, err := netip.ParseAddr(r.StartAddress)
   952  				if err != nil {
   953  					return nil, fmt.Errorf("error parsing start IP address in range '%s': %s", r.StartAddress, err)
   954  				}
   955  
   956  				// if we have end address specified - a range of IPs must be expanded into slice
   957  				// with all IPs in that range
   958  				if r.EndAddress != "" {
   959  					endIp, err := netip.ParseAddr(r.EndAddress)
   960  					if err != nil {
   961  						return nil, fmt.Errorf("error parsing end IP address in range '%s': %s", r.EndAddress, err)
   962  					}
   963  
   964  					// Check if EndAddress is lower than StartAddress ant report an error if so
   965  					if endIp.Less(startIp) {
   966  						return nil, fmt.Errorf("end IP is lower that start IP (%s < %s)", r.EndAddress, r.StartAddress)
   967  					}
   968  
   969  					// loop over IPs in range from startIp to endIp and add them to the slice one by one
   970  					// Expression 'ip.Compare(endIp) == 1'  means that 'ip > endIp' and the loop should stop
   971  					for ip := startIp; ip.Compare(endIp) != 1; ip = ip.Next() {
   972  						assignedIpSlice = append(assignedIpSlice, ip)
   973  					}
   974  				} else { // if there is no end address in the range, then it is only a single IP - startIp
   975  					assignedIpSlice = append(assignedIpSlice, startIp)
   976  				}
   977  			}
   978  		}
   979  	}
   980  
   981  	return assignedIpSlice, nil
   982  }
   983  
   984  // ipSliceDifference performs mathematical subtraction for two slices of IPs
   985  // The formula is (minuend − subtrahend = difference)
   986  //
   987  // Special behavior:
   988  // * Passing nil minuend results in nil
   989  // * Passing nil subtrahend will return minuendSlice
   990  //
   991  // NOTE. This function will mutate minuendSlice to save memory and avoid having a copy of all values
   992  // which can become expensive if there are a lot of items
   993  func ipSliceDifference(minuendSlice, subtrahendSlice []netip.Addr) []netip.Addr {
   994  	if minuendSlice == nil {
   995  		return nil
   996  	}
   997  
   998  	if subtrahendSlice == nil {
   999  		return minuendSlice
  1000  	}
  1001  
  1002  	// Removal of elements from an empty slice results in an empty slice
  1003  	if len(minuendSlice) == 0 {
  1004  		return []netip.Addr{}
  1005  	}
  1006  	// Having an empty subtrahendSlice results in minuendSlice
  1007  	if len(subtrahendSlice) == 0 {
  1008  		return minuendSlice
  1009  	}
  1010  
  1011  	resultIpCount := 0 // count of IPs after removing items from subtrahendSlice
  1012  
  1013  	// Loop over minuend IPs
  1014  	for _, minuendIp := range minuendSlice {
  1015  
  1016  		// Check if subtrahend has minuend element listed
  1017  		var foundSubtrahend bool
  1018  		for _, subtrahendIp := range subtrahendSlice {
  1019  			if subtrahendIp == minuendIp {
  1020  				// IP found in subtrahend, therefore breaking inner loop early
  1021  				foundSubtrahend = true
  1022  				break
  1023  			}
  1024  		}
  1025  
  1026  		// Store the IP in `minuendSlice` at `resultIpCount` index and increment the index itself
  1027  		if !foundSubtrahend {
  1028  			// Add IP to the 'resultIpCount' index position
  1029  			minuendSlice[resultIpCount] = minuendIp
  1030  			resultIpCount++
  1031  		}
  1032  	}
  1033  
  1034  	// if all elements are removed - return nil
  1035  	if resultIpCount == 0 {
  1036  		return nil
  1037  	}
  1038  
  1039  	// cut off all values, greater than `resultIpCount`
  1040  	minuendSlice = minuendSlice[:resultIpCount]
  1041  
  1042  	return minuendSlice
  1043  }
  1044  
  1045  // filterIpSlicesBySubnet accepts 'ipRange' and returns a slice of IPs only that fall into given
  1046  // 'subnet' leaving everything out
  1047  //
  1048  // Special behavior:
  1049  // * Passing empty 'subnet' will return `nil` and an error
  1050  // * Passing empty 'ipRange' will return 'nil' and an error
  1051  //
  1052  // Note. This function does not enforce uniqueness of IPs in 'ipRange' and if there are duplicate
  1053  // IPs matching 'subnet' they will be in the resulting slice
  1054  func filterIpSlicesBySubnet(ipRange []netip.Addr, subnet netip.Prefix) ([]netip.Addr, error) {
  1055  	if subnet == (netip.Prefix{}) {
  1056  		return nil, fmt.Errorf("empty subnet specified")
  1057  	}
  1058  
  1059  	if len(ipRange) == 0 {
  1060  		return nil, fmt.Errorf("empty IP Range specified")
  1061  	}
  1062  
  1063  	filteredRange := make([]netip.Addr, 0)
  1064  
  1065  	for _, ip := range ipRange {
  1066  		if subnet.Contains(ip) {
  1067  			filteredRange = append(filteredRange, ip)
  1068  		}
  1069  	}
  1070  
  1071  	return filteredRange, nil
  1072  }
  1073  
  1074  // flattenGatewayUsedIpAddressesToIpSlice accepts a slice of `GatewayUsedIpAddress` coming directly
  1075  // from the API and converts it to slice of Go's native '[]netip.Addr' which supports IPv4 and IPv6
  1076  func flattenGatewayUsedIpAddressesToIpSlice(usedIpAddresses []*types.GatewayUsedIpAddress) ([]netip.Addr, error) {
  1077  	usedIpSlice := make([]netip.Addr, len(usedIpAddresses))
  1078  	for usedIpIndex := range usedIpAddresses {
  1079  		ip, err := netip.ParseAddr(usedIpAddresses[usedIpIndex].IPAddress)
  1080  		if err != nil {
  1081  			return nil, fmt.Errorf("error parsing IP '%s' in Edge Gateway used IP address list: %s", usedIpAddresses[usedIpIndex].IPAddress, err)
  1082  		}
  1083  		usedIpSlice[usedIpIndex] = ip
  1084  	}
  1085  
  1086  	return usedIpSlice, nil
  1087  }
  1088  
  1089  func reorderEdgeGatewayUplinks(edgeGatewayUplinks []types.EdgeGatewayUplinks) []types.EdgeGatewayUplinks {
  1090  	// If only 1 uplink is present - there is nothing to reorder, because only mandatory uplink is present
  1091  	if len(edgeGatewayUplinks) == 1 {
  1092  		return edgeGatewayUplinks
  1093  	}
  1094  
  1095  	// Element 0 is External Network backed by Tier 0 Gateway or T0 Gateway VRF - nothing to do
  1096  	if edgeGatewayUplinks[0].BackingType != nil && (*edgeGatewayUplinks[0].BackingType == "NSXT_TIER0" || *edgeGatewayUplinks[0].BackingType == "NSXT_VRF_TIER0") {
  1097  		return edgeGatewayUplinks
  1098  	}
  1099  
  1100  	for uplinkIndex := range edgeGatewayUplinks {
  1101  		if edgeGatewayUplinks[uplinkIndex].BackingType != nil && (*edgeGatewayUplinks[uplinkIndex].BackingType == "NSXT_TIER0" || *edgeGatewayUplinks[uplinkIndex].BackingType == "NSXT_VRF_TIER0") {
  1102  			// Swap elements so that 'NSXT_TIER0' or 'NSXT_VRF_TIER0' is at position 0
  1103  			t0BackedUplink := edgeGatewayUplinks[uplinkIndex]
  1104  			edgeGatewayUplinks[uplinkIndex] = edgeGatewayUplinks[0]
  1105  			edgeGatewayUplinks[0] = t0BackedUplink
  1106  
  1107  			return edgeGatewayUplinks
  1108  		}
  1109  	}
  1110  
  1111  	return edgeGatewayUplinks
  1112  }