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

     1  /*
     2   * Copyright 2019 VMware, Inc.  All rights reserved.  Licensed under the Apache v2 License.
     3   */
     4  
     5  package govcd
     6  
     7  import (
     8  	"bytes"
     9  	"crypto/rand"
    10  	"encoding/xml"
    11  	"fmt"
    12  	"net/http"
    13  	"net/url"
    14  	"regexp"
    15  	"strconv"
    16  	"strings"
    17  	"time"
    18  
    19  	"github.com/vmware/go-vcloud-director/v2/types/v56"
    20  	"github.com/vmware/go-vcloud-director/v2/util"
    21  )
    22  
    23  type EdgeGateway struct {
    24  	EdgeGateway *types.EdgeGateway
    25  	client      *Client
    26  }
    27  
    28  // Simplified structure used to list networks connected to an edge gateway
    29  type SimpleNetworkIdentifier struct {
    30  	Name          string
    31  	InterfaceType string
    32  }
    33  
    34  var reErrorBusy = regexp.MustCompile(`is busy completing an operation.$`)
    35  
    36  func NewEdgeGateway(cli *Client) *EdgeGateway {
    37  	return &EdgeGateway{
    38  		EdgeGateway: new(types.EdgeGateway),
    39  		client:      cli,
    40  	}
    41  }
    42  
    43  // Struct which covers NAT rule fields
    44  type NatRule struct {
    45  	NatType      string
    46  	NetworkHref  string
    47  	ExternalIP   string
    48  	ExternalPort string
    49  	InternalIP   string
    50  	InternalPort string
    51  	Protocol     string
    52  	IcmpSubType  string
    53  	Description  string
    54  }
    55  
    56  // AddDhcpPool adds (or updates) the DHCP pool connected to a specific network.
    57  // TODO: this is legacy code from 2015, which requires a Terraform structure to work. It may need some re-thinking.
    58  func (egw *EdgeGateway) AddDhcpPool(network *types.OrgVDCNetwork, dhcppool []interface{}) (Task, error) {
    59  	newEdgeConfig := egw.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration
    60  	util.Logger.Printf("[DEBUG] EDGE GATEWAY: %#v", newEdgeConfig)
    61  	util.Logger.Printf("[DEBUG] EDGE GATEWAY SERVICE: %#v", newEdgeConfig.GatewayDhcpService)
    62  	newDchpService := &types.GatewayDhcpService{}
    63  	if newEdgeConfig.GatewayDhcpService.Pool == nil {
    64  		newDchpService.IsEnabled = true
    65  	} else {
    66  		newDchpService.IsEnabled = newEdgeConfig.GatewayDhcpService.IsEnabled
    67  
    68  		for _, dhcpPoolService := range newEdgeConfig.GatewayDhcpService.Pool {
    69  
    70  			// Kludgy IF to avoid deleting DNAT rules not created by us.
    71  			// If matches, let's skip it and continue the loop
    72  			// Note: a simple comparison of HREF fields may fail if one of them is
    73  			// from a tenant object and the other from a provider object. They may have the
    74  			// same ID but different paths. Using 'equalIds' we determine equality even with
    75  			// different paths
    76  			if equalIds(network.HREF, "", dhcpPoolService.Network.HREF) {
    77  				continue
    78  			}
    79  
    80  			newDchpService.Pool = append(newDchpService.Pool, dhcpPoolService)
    81  		}
    82  	}
    83  
    84  	for _, item := range dhcppool {
    85  		data := item.(map[string]interface{})
    86  
    87  		if data["default_lease_time"] == nil {
    88  			data["default_lease_time"] = 3600
    89  		}
    90  
    91  		if data["max_lease_time"] == nil {
    92  			data["max_lease_time"] = 7200
    93  		}
    94  
    95  		dhcpRule := &types.DhcpPoolService{
    96  			IsEnabled: true,
    97  			Network: &types.Reference{
    98  				HREF: network.HREF,
    99  				Name: network.Name,
   100  			},
   101  			DefaultLeaseTime: data["default_lease_time"].(int),
   102  			MaxLeaseTime:     data["max_lease_time"].(int),
   103  			LowIPAddress:     data["start_address"].(string),
   104  			HighIPAddress:    data["end_address"].(string),
   105  		}
   106  		newDchpService.Pool = append(newDchpService.Pool, dhcpRule)
   107  	}
   108  
   109  	newRules := &types.EdgeGatewayServiceConfiguration{
   110  		Xmlns:              types.XMLNamespaceVCloud,
   111  		GatewayDhcpService: newDchpService,
   112  	}
   113  
   114  	output, err := xml.MarshalIndent(newRules, "  ", "    ")
   115  	if err != nil {
   116  		return Task{}, fmt.Errorf("error reconfiguring Edge Gateway: %s", err)
   117  	}
   118  
   119  	var resp *http.Response
   120  	for {
   121  		buffer := bytes.NewBufferString(xml.Header + string(output))
   122  
   123  		apiEndpoint := urlParseRequestURI(egw.EdgeGateway.HREF)
   124  		apiEndpoint.Path += "/action/configureServices"
   125  
   126  		req := egw.client.NewRequest(map[string]string{}, http.MethodPost, *apiEndpoint, buffer)
   127  		util.Logger.Printf("[DEBUG] POSTING TO URL: %s", apiEndpoint.Path)
   128  		util.Logger.Printf("[DEBUG] XML TO SEND:\n%s", buffer)
   129  
   130  		req.Header.Add("Content-Type", "application/vnd.vmware.admin.edgeGatewayServiceConfiguration+xml")
   131  
   132  		resp, err = checkResp(egw.client.Http.Do(req))
   133  		if err != nil {
   134  			if reErrorBusy.MatchString(err.Error()) {
   135  				time.Sleep(3 * time.Second)
   136  				continue
   137  			}
   138  			return Task{}, fmt.Errorf("error reconfiguring Edge Gateway: %s", err)
   139  		}
   140  		break
   141  	}
   142  
   143  	task := NewTask(egw.client)
   144  
   145  	if err = decodeBody(types.BodyTypeXML, resp, task.Task); err != nil {
   146  		return Task{}, fmt.Errorf("error decoding Task response: %s", err)
   147  	}
   148  
   149  	// The request was successful
   150  	return *task, nil
   151  
   152  }
   153  
   154  // Deprecated: use one of RemoveNATRuleAsync, RemoveNATRule
   155  func (egw *EdgeGateway) RemoveNATMapping(natType, externalIP, internalIP, port string) (Task, error) {
   156  	return egw.RemoveNATPortMapping(natType, externalIP, port, internalIP, port)
   157  }
   158  
   159  // Deprecated: use one of RemoveNATRuleAsync, RemoveNATRule
   160  func (egw *EdgeGateway) RemoveNATPortMapping(natType, externalIP, externalPort, internalIP, internalPort string) (Task, error) {
   161  	// Find uplink interface
   162  	var uplink types.Reference
   163  	for _, gi := range egw.EdgeGateway.Configuration.GatewayInterfaces.GatewayInterface {
   164  		if gi.InterfaceType != "uplink" {
   165  			continue
   166  		}
   167  		uplink = *gi.Network
   168  	}
   169  
   170  	newEdgeConfig := egw.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration
   171  
   172  	// Take care of the NAT service
   173  	newNatService := &types.NatService{}
   174  
   175  	newNatService.IsEnabled = newEdgeConfig.NatService.IsEnabled
   176  	newNatService.NatType = newEdgeConfig.NatService.NatType
   177  	newNatService.Policy = newEdgeConfig.NatService.Policy
   178  	newNatService.ExternalIP = newEdgeConfig.NatService.ExternalIP
   179  
   180  	for _, natRule := range newEdgeConfig.NatService.NatRule {
   181  
   182  		if natRule.RuleType == natType &&
   183  			natRule.GatewayNatRule.OriginalIP == externalIP &&
   184  			natRule.GatewayNatRule.OriginalPort == externalPort &&
   185  			natRule.GatewayNatRule.Interface.HREF == uplink.HREF {
   186  			util.Logger.Printf("[DEBUG] REMOVING %s Rule: %#v", natRule.RuleType, natRule.GatewayNatRule)
   187  			continue
   188  		}
   189  		util.Logger.Printf("[DEBUG] KEEPING %s Rule: %#v", natRule.RuleType, natRule.GatewayNatRule)
   190  		newNatService.NatRule = append(newNatService.NatRule, natRule)
   191  	}
   192  
   193  	newEdgeConfig.NatService = newNatService
   194  
   195  	newRules := &types.EdgeGatewayServiceConfiguration{
   196  		Xmlns:      types.XMLNamespaceVCloud,
   197  		NatService: newNatService,
   198  	}
   199  
   200  	apiEndpoint := urlParseRequestURI(egw.EdgeGateway.HREF)
   201  	apiEndpoint.Path += "/action/configureServices"
   202  
   203  	// Return the task
   204  	return egw.client.ExecuteTaskRequest(apiEndpoint.String(), http.MethodPost,
   205  		"application/vnd.vmware.admin.edgeGatewayServiceConfiguration+xml", "error reconfiguring Edge Gateway: %s", newRules)
   206  
   207  }
   208  
   209  // RemoveNATRule removes NAT removes NAT rule identified by ID and handles task. Returns error if issues rise.
   210  // Old functions RemoveNATPortMapping and RemoveNATMapping removed using rule details
   211  // and expected interface to be of external network type.
   212  func (egw *EdgeGateway) RemoveNATRule(id string) error {
   213  	task, err := egw.RemoveNATRuleAsync(id)
   214  	if err != nil {
   215  		return fmt.Errorf("error removing DNAT rule: %s", err)
   216  	}
   217  	err = task.WaitTaskCompletion()
   218  	if err != nil {
   219  		return fmt.Errorf("%s", combinedTaskErrorMessage(task.Task, err))
   220  	}
   221  
   222  	return nil
   223  }
   224  
   225  // RemoveNATRuleAsync removes NAT rule or returns an error.
   226  // Old functions RemoveNATPortMapping and RemoveNATMapping removed using rule details
   227  // and expected interface to be of external network type.
   228  func (egw *EdgeGateway) RemoveNATRuleAsync(id string) (Task, error) {
   229  	if id == "" {
   230  		return Task{}, fmt.Errorf("provided id is empty")
   231  	}
   232  
   233  	err := egw.Refresh()
   234  	if err != nil {
   235  		return Task{}, fmt.Errorf("error refreshing edge gateway: %s", err)
   236  	}
   237  
   238  	natServiceToUpdate := egw.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.NatService
   239  	ruleIndex := -1
   240  	if natServiceToUpdate != nil {
   241  		for n, existingNatRule := range natServiceToUpdate.NatRule {
   242  			if existingNatRule.ID == id {
   243  				ruleIndex = n
   244  				break
   245  			}
   246  		}
   247  	} else {
   248  		return Task{}, fmt.Errorf("edge gateway doesn't have NAT rules")
   249  	}
   250  
   251  	if ruleIndex == -1 {
   252  		return Task{}, fmt.Errorf("edge gateway doesn't have rule with such ID")
   253  	}
   254  
   255  	if len(natServiceToUpdate.NatRule) > 1 {
   256  		natServiceToUpdate.NatRule = append(natServiceToUpdate.NatRule[:ruleIndex], natServiceToUpdate.NatRule[ruleIndex+1:]...)
   257  	} else {
   258  		natServiceToUpdate.NatRule = nil
   259  	}
   260  
   261  	newRules := &types.EdgeGatewayServiceConfiguration{
   262  		Xmlns:      types.XMLNamespaceVCloud,
   263  		NatService: natServiceToUpdate,
   264  	}
   265  
   266  	egwConfigureHref := urlParseRequestURI(egw.EdgeGateway.HREF)
   267  	egwConfigureHref.Path += "/action/configureServices"
   268  
   269  	// Return the task
   270  	return egw.client.ExecuteTaskRequest(egwConfigureHref.String(), http.MethodPost,
   271  		"application/vnd.vmware.admin.edgeGatewayServiceConfiguration+xml", "error reconfiguring Edge Gateway: %s", newRules)
   272  }
   273  
   274  // AddDNATRule creates DNAT rule and returns the NAT struct that was created or an error.
   275  // Allows assigning a specific Org VDC or an external network.
   276  // When edge gateway is advanced vCD API uses element <tag> to map with NSX edge gateway ID. A known issue is
   277  // that updating rule using User interface resets <tag> and as result mapping is lost.
   278  // Getting using NatRule.ID won't be valid anymore.
   279  // Old functions AddNATPortMapping and AddNATMapping assigned rule only to first external network
   280  func (egw *EdgeGateway) AddDNATRule(ruleDetails NatRule) (*types.NatRule, error) {
   281  	mappingId, err := getPseudoUuid()
   282  	if err != nil {
   283  		return nil, err
   284  	}
   285  	originalDescription := ruleDetails.Description
   286  	ruleDetails.Description = mappingId
   287  
   288  	ruleDetails.NatType = "DNAT"
   289  	task, err := egw.AddNATRuleAsync(ruleDetails)
   290  	if err != nil {
   291  		return nil, fmt.Errorf("error creating DNAT rule: %s", err)
   292  	}
   293  	err = task.WaitTaskCompletion()
   294  	if err != nil {
   295  		return nil, fmt.Errorf("%s", combinedTaskErrorMessage(task.Task, err))
   296  	}
   297  
   298  	var createdNatRule *types.NatRule
   299  
   300  	err = egw.Refresh()
   301  	if err != nil {
   302  		return nil, fmt.Errorf("error refreshing edge gateway: %s", err)
   303  	}
   304  
   305  	for _, natRule := range egw.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.NatService.NatRule {
   306  		if natRule.Description == mappingId {
   307  			createdNatRule = natRule
   308  			break
   309  		}
   310  	}
   311  
   312  	if createdNatRule == nil {
   313  		return nil, fmt.Errorf("error creating DNAT rule, didn't match created rule")
   314  	}
   315  
   316  	createdNatRule.Description = originalDescription
   317  
   318  	return egw.UpdateNatRule(createdNatRule)
   319  }
   320  
   321  // AddSNATRule creates SNAT rule and returns created NAT rule or error.
   322  // Allows assigning a specific Org VDC or an external network.
   323  // Old functions AddNATPortMapping and AddNATMapping aren't correct as assigned rule only to first external network
   324  func (egw *EdgeGateway) AddSNATRule(networkHref, externalIP, internalIP, description string) (*types.NatRule, error) {
   325  
   326  	// As vCD API doesn't return rule ID we get it manually:
   327  	//  * create rule with description which value is our generated ID
   328  	//  * find rule which has description with our generated ID
   329  	//  * get the real (vCD's) rule ID
   330  	//  * update description with real value and return nat rule
   331  
   332  	mappingId, err := getPseudoUuid()
   333  	if err != nil {
   334  		return nil, err
   335  	}
   336  
   337  	task, err := egw.AddNATRuleAsync(NatRule{NetworkHref: networkHref, NatType: "SNAT", ExternalIP: externalIP,
   338  		ExternalPort: "any", InternalIP: internalIP, InternalPort: "any",
   339  		IcmpSubType: "", Protocol: "any", Description: mappingId})
   340  	if err != nil {
   341  		return nil, fmt.Errorf("error creating SNAT rule: %s", err)
   342  	}
   343  	err = task.WaitTaskCompletion()
   344  	if err != nil {
   345  		return nil, fmt.Errorf("%s", combinedTaskErrorMessage(task.Task, err))
   346  	}
   347  
   348  	var createdNatRule *types.NatRule
   349  
   350  	err = egw.Refresh()
   351  	if err != nil {
   352  		return nil, fmt.Errorf("error refreshing edge gateway: %s", err)
   353  	}
   354  
   355  	for _, natRule := range egw.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.NatService.NatRule {
   356  		if natRule.Description == mappingId {
   357  			createdNatRule = natRule
   358  			break
   359  		}
   360  	}
   361  
   362  	if createdNatRule == nil {
   363  		return nil, fmt.Errorf("error creating SNAT rule, didn't match created rule")
   364  	}
   365  
   366  	createdNatRule.Description = description
   367  
   368  	return egw.UpdateNatRule(createdNatRule)
   369  }
   370  
   371  // getPseudoUuid creates unique ID/UUID
   372  func getPseudoUuid() (string, error) {
   373  
   374  	b := make([]byte, 16)
   375  	_, err := rand.Read(b)
   376  	if err != nil {
   377  		return "", err
   378  	}
   379  
   380  	uuid := fmt.Sprintf("%X-%X-%X-%X-%X", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
   381  
   382  	return uuid, nil
   383  }
   384  
   385  // UpdateNatRule updates NAT rule and handles task. Returns updated NAT rule or error.
   386  func (egw *EdgeGateway) UpdateNatRule(natRule *types.NatRule) (*types.NatRule, error) {
   387  	task, err := egw.UpdateNatRuleAsync(natRule)
   388  	if err != nil {
   389  		return nil, fmt.Errorf("error updating NAT rule: %s", err)
   390  	}
   391  	err = task.WaitTaskCompletion()
   392  	if err != nil {
   393  		return nil, fmt.Errorf("%s", combinedTaskErrorMessage(task.Task, err))
   394  	}
   395  
   396  	return egw.GetNatRule(natRule.ID)
   397  }
   398  
   399  // UpdateNatRuleAsync updates NAT rule and returns task or error.
   400  func (egw *EdgeGateway) UpdateNatRuleAsync(natRule *types.NatRule) (Task, error) {
   401  	if natRule.GatewayNatRule.Protocol != "" && !isValidProtocol(natRule.GatewayNatRule.Protocol) {
   402  		return Task{}, fmt.Errorf("provided protocol is not one of TCP, UDP, TCPUDP, ICMP, ANY")
   403  	}
   404  
   405  	if strings.ToUpper(natRule.GatewayNatRule.Protocol) == "ICMP" && !isValidIcmpSubType(natRule.GatewayNatRule.IcmpSubType) {
   406  		return Task{}, fmt.Errorf("provided icmp sub type is not correct")
   407  	}
   408  
   409  	err := egw.Refresh()
   410  	if err != nil {
   411  		return Task{}, fmt.Errorf("error refreshing edge gateway: %s", err)
   412  	}
   413  
   414  	natServiceToUpdate := egw.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.NatService
   415  
   416  	if natServiceToUpdate != nil {
   417  		for n, existingNatRule := range natServiceToUpdate.NatRule {
   418  			if existingNatRule.ID == natRule.ID {
   419  				natServiceToUpdate.NatRule[n] = natRule
   420  			}
   421  		}
   422  	} else {
   423  		return Task{}, fmt.Errorf("edge gateway doesn't have such nat rule")
   424  	}
   425  
   426  	newRules := &types.EdgeGatewayServiceConfiguration{
   427  		Xmlns:      types.XMLNamespaceVCloud,
   428  		NatService: natServiceToUpdate,
   429  	}
   430  
   431  	egwConfigureHref := urlParseRequestURI(egw.EdgeGateway.HREF)
   432  	egwConfigureHref.Path += "/action/configureServices"
   433  
   434  	// Return the task
   435  	return egw.client.ExecuteTaskRequest(egwConfigureHref.String(), http.MethodPost,
   436  		"application/vnd.vmware.admin.edgeGatewayServiceConfiguration+xml", "error reconfiguring Edge Gateway: %s", newRules)
   437  }
   438  
   439  // GetNatRule returns NAT rule or error.
   440  func (egw *EdgeGateway) GetNatRule(id string) (*types.NatRule, error) {
   441  	err := egw.Refresh()
   442  	if err != nil {
   443  		return nil, fmt.Errorf("error refreshing edge gateway: %s", err)
   444  	}
   445  
   446  	if egw.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.NatService != nil {
   447  		for _, natRule := range egw.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.NatService.NatRule {
   448  			if natRule.ID == id {
   449  				return natRule, nil
   450  			}
   451  		}
   452  	}
   453  
   454  	return nil, ErrorEntityNotFound
   455  }
   456  
   457  // AddNATRuleAsync creates NAT rule and return task or err
   458  // Allows assigning specific network Org VDC or external. Old function AddNATPortMapping and
   459  // AddNATMapping function shouldn't be used because assigns rule to first external network
   460  func (egw *EdgeGateway) AddNATRuleAsync(ruleDetails NatRule) (Task, error) {
   461  	if !isValidProtocol(ruleDetails.Protocol) {
   462  		return Task{}, fmt.Errorf("provided protocol is not one of TCP, UDP, TCPUDP, ICMP, ANY")
   463  	}
   464  
   465  	if strings.ToUpper(ruleDetails.Protocol) == "ICMP" && !isValidIcmpSubType(ruleDetails.IcmpSubType) {
   466  		return Task{}, fmt.Errorf("provided icmp sub type is not correct")
   467  	}
   468  
   469  	currentEdgeConfig := egw.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration
   470  
   471  	// Take care of the NAT service
   472  	newNatService := &types.NatService{}
   473  
   474  	if currentEdgeConfig.NatService == nil {
   475  		newNatService.IsEnabled = true
   476  	} else {
   477  		newNatService.IsEnabled = currentEdgeConfig.NatService.IsEnabled
   478  		newNatService.NatType = currentEdgeConfig.NatService.NatType
   479  		newNatService.Policy = currentEdgeConfig.NatService.Policy
   480  		newNatService.ExternalIP = currentEdgeConfig.NatService.ExternalIP
   481  		newNatService.NatRule = currentEdgeConfig.NatService.NatRule
   482  	}
   483  
   484  	//construct new rule
   485  	natRule := &types.NatRule{
   486  		RuleType:    ruleDetails.NatType,
   487  		IsEnabled:   addrOf(true),
   488  		Description: ruleDetails.Description,
   489  		GatewayNatRule: &types.GatewayNatRule{
   490  			Interface: &types.Reference{
   491  				HREF: ruleDetails.NetworkHref,
   492  			},
   493  			OriginalIP:     ruleDetails.ExternalIP,
   494  			OriginalPort:   ruleDetails.ExternalPort,
   495  			TranslatedIP:   ruleDetails.InternalIP,
   496  			TranslatedPort: ruleDetails.InternalPort,
   497  			Protocol:       ruleDetails.Protocol,
   498  			IcmpSubType:    ruleDetails.IcmpSubType,
   499  		},
   500  	}
   501  
   502  	newNatService.NatRule = append(newNatService.NatRule, natRule)
   503  	currentEdgeConfig.NatService = newNatService
   504  	newRules := &types.EdgeGatewayServiceConfiguration{
   505  		Xmlns:      types.XMLNamespaceVCloud,
   506  		NatService: newNatService,
   507  	}
   508  
   509  	egwConfigureHref := urlParseRequestURI(egw.EdgeGateway.HREF)
   510  	egwConfigureHref.Path += "/action/configureServices"
   511  
   512  	// Return the task
   513  	return egw.client.ExecuteTaskRequest(egwConfigureHref.String(), http.MethodPost,
   514  		"application/vnd.vmware.admin.edgeGatewayServiceConfiguration+xml", "error reconfiguring Edge Gateway: %s", newRules)
   515  }
   516  
   517  // Deprecated: Use eGW.AddSNATRule() or eGW.AddDNATRule()
   518  func (egw *EdgeGateway) AddNATRule(network *types.OrgVDCNetwork, natType, externalIP, internalIP string) (Task, error) {
   519  	return egw.AddNATPortMappingWithUplink(network, natType, externalIP, "any", internalIP, "any", "any", "")
   520  }
   521  
   522  // Deprecated: Use eGW.AddNATRule()
   523  func (egw *EdgeGateway) AddNATMapping(natType, externalIP, internalIP string) (Task, error) {
   524  	return egw.AddNATPortMapping(natType, externalIP, "any", internalIP, "any", "any", "")
   525  }
   526  
   527  // Deprecated: Use eGW.AddNATPortMappingWithUplink()
   528  func (egw *EdgeGateway) AddNATPortMapping(natType, externalIP, externalPort, internalIP, internalPort, protocol, icmpSubType string) (Task, error) {
   529  	return egw.AddNATPortMappingWithUplink(nil, natType, externalIP, externalPort, internalIP, internalPort, protocol, icmpSubType)
   530  }
   531  
   532  // Deprecated: creates not good behaviour of functionality
   533  func (egw *EdgeGateway) getFirstUplink() types.Reference {
   534  	var uplink types.Reference
   535  	for _, gi := range egw.EdgeGateway.Configuration.GatewayInterfaces.GatewayInterface {
   536  		if gi.InterfaceType != "uplink" {
   537  			continue
   538  		}
   539  		uplink = *gi.Network
   540  	}
   541  	return uplink
   542  }
   543  
   544  // Values are matched with VCD UI when creating DNAT for edge gateway.
   545  func isValidProtocol(protocol string) bool {
   546  	switch strings.ToUpper(protocol) {
   547  	case
   548  		"TCP",
   549  		"UDP",
   550  		"TCPUDP",
   551  		"ICMP",
   552  		"ANY":
   553  		return true
   554  	}
   555  	return false
   556  }
   557  
   558  // Used values are named here https://code.vmware.com/apis/287/vcloud#/doc/doc/types/GatewayNatRuleType.html
   559  // Also can be matched in VCD UI when creating DNAT for edge gateway.
   560  func isValidIcmpSubType(protocol string) bool {
   561  	switch strings.ToLower(protocol) {
   562  	case
   563  		"address-mask-request",
   564  		"address-mask-reply",
   565  		"destination-unreachable",
   566  		"echo-request",
   567  		"echo-reply",
   568  		"parameter-problem",
   569  		"redirect",
   570  		"router-advertisement",
   571  		"router-solicitation",
   572  		"source-quench",
   573  		"time-exceeded",
   574  		"timestamp-request",
   575  		"timestamp-reply",
   576  		"any":
   577  		return true
   578  	}
   579  	return false
   580  }
   581  
   582  // Deprecated: Use eGW.AddDNATRule() or eGW.CreateNsxvNatRule() for NSX-V
   583  func (egw *EdgeGateway) AddNATPortMappingWithUplink(network *types.OrgVDCNetwork, natType, externalIP, externalPort, internalIP, internalPort, protocol, icmpSubType string) (Task, error) {
   584  	// if a network is provided take it, otherwise find first uplink on the edge gateway
   585  	var uplinkRef string
   586  
   587  	if network != nil {
   588  		uplinkRef = network.HREF
   589  	} else {
   590  		// TODO: remove when method used this removed
   591  		uplinkRef = egw.getFirstUplink().HREF
   592  	}
   593  
   594  	if !isValidProtocol(protocol) {
   595  		return Task{}, fmt.Errorf("provided protocol is not one of TCP, UDP, TCPUDP, ICMP, ANY")
   596  	}
   597  
   598  	if strings.ToUpper(protocol) == "ICMP" && !isValidIcmpSubType(icmpSubType) {
   599  		return Task{}, fmt.Errorf("provided icmp sub type is not correct")
   600  	}
   601  
   602  	newEdgeConfig := egw.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration
   603  
   604  	// Take care of the NAT service
   605  	newNatService := &types.NatService{}
   606  
   607  	if newEdgeConfig.NatService == nil {
   608  		newNatService.IsEnabled = true
   609  	} else {
   610  		newNatService.IsEnabled = newEdgeConfig.NatService.IsEnabled
   611  		newNatService.NatType = newEdgeConfig.NatService.NatType
   612  		newNatService.Policy = newEdgeConfig.NatService.Policy
   613  		newNatService.ExternalIP = newEdgeConfig.NatService.ExternalIP
   614  
   615  		for _, natRule := range newEdgeConfig.NatService.NatRule {
   616  
   617  			// Kludgy IF to avoid deleting DNAT rules not created by us.
   618  			// If matches, let's skip it and continue the loop
   619  			if natRule.RuleType == natType &&
   620  				natRule.GatewayNatRule.OriginalIP == externalIP &&
   621  				natRule.GatewayNatRule.OriginalPort == externalPort &&
   622  				natRule.GatewayNatRule.TranslatedIP == internalIP &&
   623  				natRule.GatewayNatRule.TranslatedPort == internalPort &&
   624  				natRule.GatewayNatRule.Interface.HREF == uplinkRef {
   625  				continue
   626  			}
   627  
   628  			newNatService.NatRule = append(newNatService.NatRule, natRule)
   629  		}
   630  	}
   631  
   632  	//add rule
   633  	natRule := &types.NatRule{
   634  		RuleType:  natType,
   635  		IsEnabled: addrOf(true),
   636  		GatewayNatRule: &types.GatewayNatRule{
   637  			Interface: &types.Reference{
   638  				HREF: uplinkRef,
   639  			},
   640  			OriginalIP:     externalIP,
   641  			OriginalPort:   externalPort,
   642  			TranslatedIP:   internalIP,
   643  			TranslatedPort: internalPort,
   644  			Protocol:       protocol,
   645  			IcmpSubType:    icmpSubType,
   646  		},
   647  	}
   648  	newNatService.NatRule = append(newNatService.NatRule, natRule)
   649  
   650  	newEdgeConfig.NatService = newNatService
   651  
   652  	newRules := &types.EdgeGatewayServiceConfiguration{
   653  		Xmlns:      types.XMLNamespaceVCloud,
   654  		NatService: newNatService,
   655  	}
   656  
   657  	apiEndpoint := urlParseRequestURI(egw.EdgeGateway.HREF)
   658  	apiEndpoint.Path += "/action/configureServices"
   659  
   660  	// Return the task
   661  	return egw.client.ExecuteTaskRequest(apiEndpoint.String(), http.MethodPost,
   662  		"application/vnd.vmware.admin.edgeGatewayServiceConfiguration+xml", "error reconfiguring Edge Gateway: %s", newRules)
   663  }
   664  
   665  func (egw *EdgeGateway) CreateFirewallRules(defaultAction string, rules []*types.FirewallRule) (Task, error) {
   666  	err := egw.Refresh()
   667  	if err != nil {
   668  		return Task{}, fmt.Errorf("error: %s", err)
   669  	}
   670  
   671  	newRules := &types.EdgeGatewayServiceConfiguration{
   672  		Xmlns: types.XMLNamespaceVCloud,
   673  		FirewallService: &types.FirewallService{
   674  			IsEnabled:        true,
   675  			DefaultAction:    defaultAction,
   676  			LogDefaultAction: true,
   677  			FirewallRule:     rules,
   678  		},
   679  	}
   680  
   681  	output, err := xml.MarshalIndent(newRules, "  ", "    ")
   682  	if err != nil {
   683  		return Task{}, fmt.Errorf("error: %s", err)
   684  	}
   685  
   686  	var resp *http.Response
   687  	for {
   688  		buffer := bytes.NewBufferString(xml.Header + string(output))
   689  
   690  		apiEndpoint := urlParseRequestURI(egw.EdgeGateway.HREF)
   691  		apiEndpoint.Path += "/action/configureServices"
   692  
   693  		req := egw.client.NewRequest(map[string]string{}, http.MethodPost, *apiEndpoint, buffer)
   694  		util.Logger.Printf("[DEBUG] POSTING TO URL: %s", apiEndpoint.Path)
   695  		util.Logger.Printf("[DEBUG] XML TO SEND:\n%s", buffer)
   696  
   697  		req.Header.Add("Content-Type", "application/vnd.vmware.admin.edgeGatewayServiceConfiguration+xml")
   698  
   699  		resp, err = checkResp(egw.client.Http.Do(req))
   700  		if err != nil {
   701  			if reErrorBusy.MatchString(err.Error()) {
   702  				time.Sleep(3 * time.Second)
   703  				continue
   704  			}
   705  			return Task{}, fmt.Errorf("error reconfiguring Edge Gateway: %s", err)
   706  		}
   707  		break
   708  	}
   709  
   710  	task := NewTask(egw.client)
   711  
   712  	if err = decodeBody(types.BodyTypeXML, resp, task.Task); err != nil {
   713  		return Task{}, fmt.Errorf("error decoding Task response: %s", err)
   714  	}
   715  
   716  	// The request was successful
   717  	return *task, nil
   718  }
   719  
   720  func (egw *EdgeGateway) Refresh() error {
   721  
   722  	if egw.EdgeGateway == nil {
   723  		return fmt.Errorf("cannot refresh, Object is empty")
   724  	}
   725  
   726  	url := egw.EdgeGateway.HREF
   727  
   728  	// Empty struct before a new unmarshal, otherwise we end up with duplicate
   729  	// elements in slices.
   730  	egw.EdgeGateway = &types.EdgeGateway{}
   731  
   732  	_, err := egw.client.ExecuteRequest(url, http.MethodGet,
   733  		"", "error retrieving Edge Gateway: %s", nil, egw.EdgeGateway)
   734  
   735  	return err
   736  }
   737  
   738  func (egw *EdgeGateway) Remove1to1Mapping(internal, external string) (Task, error) {
   739  
   740  	// Refresh EdgeGateway rules
   741  	err := egw.Refresh()
   742  	if err != nil {
   743  		fmt.Printf("error: %s\n", err)
   744  	}
   745  
   746  	var uplinkif string
   747  	for _, gifs := range egw.EdgeGateway.Configuration.GatewayInterfaces.GatewayInterface {
   748  		if gifs.InterfaceType == "uplink" {
   749  			uplinkif = gifs.Network.HREF
   750  		}
   751  	}
   752  
   753  	newEdgeConfig := egw.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration
   754  
   755  	// Take care of the NAT service
   756  	newNatService := &types.NatService{}
   757  
   758  	// Copy over the NAT configuration
   759  	newNatService.IsEnabled = newEdgeConfig.NatService.IsEnabled
   760  	newNatService.NatType = newEdgeConfig.NatService.NatType
   761  	newNatService.Policy = newEdgeConfig.NatService.Policy
   762  	newNatService.ExternalIP = newEdgeConfig.NatService.ExternalIP
   763  
   764  	for i, natRule := range newEdgeConfig.NatService.NatRule {
   765  
   766  		// Kludgy IF to avoid deleting DNAT rules not created by us.
   767  		// If matches, let's skip it and continue the loop
   768  		if natRule.RuleType == "DNAT" &&
   769  			natRule.GatewayNatRule.OriginalIP == external &&
   770  			natRule.GatewayNatRule.TranslatedIP == internal &&
   771  			natRule.GatewayNatRule.OriginalPort == "any" &&
   772  			natRule.GatewayNatRule.TranslatedPort == "any" &&
   773  			natRule.GatewayNatRule.Protocol == "any" &&
   774  			natRule.GatewayNatRule.Interface.HREF == uplinkif {
   775  			continue
   776  		}
   777  
   778  		// Kludgy IF to avoid deleting SNAT rules not created by us.
   779  		// If matches, let's skip it and continue the loop
   780  		if natRule.RuleType == "SNAT" &&
   781  			natRule.GatewayNatRule.OriginalIP == internal &&
   782  			natRule.GatewayNatRule.TranslatedIP == external &&
   783  			natRule.GatewayNatRule.Interface.HREF == uplinkif {
   784  			continue
   785  		}
   786  
   787  		// If doesn't match the above IFs, it's something we need to preserve,
   788  		// let's add it to the new NatService struct
   789  		newNatService.NatRule = append(newNatService.NatRule, newEdgeConfig.NatService.NatRule[i])
   790  
   791  	}
   792  
   793  	// Fill the new NatService Section
   794  	newEdgeConfig.NatService = newNatService
   795  
   796  	// Take care of the Firewall service
   797  	newFwService := &types.FirewallService{}
   798  
   799  	// Copy over the firewall configuration
   800  	newFwService.IsEnabled = newEdgeConfig.FirewallService.IsEnabled
   801  	newFwService.DefaultAction = newEdgeConfig.FirewallService.DefaultAction
   802  	newFwService.LogDefaultAction = newEdgeConfig.FirewallService.LogDefaultAction
   803  
   804  	for i, firewallRule := range newEdgeConfig.FirewallService.FirewallRule {
   805  
   806  		// Kludgy IF to avoid deleting inbound FW rules not created by us.
   807  		// If matches, let's skip it and continue the loop
   808  		if firewallRule.Policy == "allow" &&
   809  			firewallRule.Protocols.Any &&
   810  			firewallRule.DestinationPortRange == "Any" &&
   811  			firewallRule.SourcePortRange == "Any" &&
   812  			firewallRule.SourceIP == "Any" &&
   813  			firewallRule.DestinationIP == external {
   814  			continue
   815  		}
   816  
   817  		// Kludgy IF to avoid deleting outbound FW rules not created by us.
   818  		// If matches, let's skip it and continue the loop
   819  		if firewallRule.Policy == "allow" &&
   820  			firewallRule.Protocols.Any &&
   821  			firewallRule.DestinationPortRange == "Any" &&
   822  			firewallRule.SourcePortRange == "Any" &&
   823  			firewallRule.SourceIP == internal &&
   824  			firewallRule.DestinationIP == "Any" {
   825  			continue
   826  		}
   827  
   828  		// If doesn't match the above IFs, it's something we need to preserve,
   829  		// let's add it to the new FirewallService struct
   830  		newFwService.FirewallRule = append(newFwService.FirewallRule, newEdgeConfig.FirewallService.FirewallRule[i])
   831  
   832  	}
   833  
   834  	// Fill the new FirewallService Section
   835  	newEdgeConfig.FirewallService = newFwService
   836  
   837  	// Fix
   838  	newEdgeConfig.NatService.IsEnabled = true
   839  
   840  	apiEndpoint := urlParseRequestURI(egw.EdgeGateway.HREF)
   841  	apiEndpoint.Path += "/action/configureServices"
   842  
   843  	// Return the task
   844  	return egw.client.ExecuteTaskRequest(apiEndpoint.String(), http.MethodPost,
   845  		"application/vnd.vmware.admin.edgeGatewayServiceConfiguration+xml", "error reconfiguring Edge Gateway: %s", newEdgeConfig)
   846  
   847  }
   848  
   849  func (egw *EdgeGateway) Create1to1Mapping(internal, external, description string) (Task, error) {
   850  
   851  	// Refresh EdgeGateway rules
   852  	err := egw.Refresh()
   853  	if err != nil {
   854  		fmt.Printf("error: %s\n", err)
   855  	}
   856  
   857  	var uplinkif string
   858  	for _, gifs := range egw.EdgeGateway.Configuration.GatewayInterfaces.GatewayInterface {
   859  		if gifs.InterfaceType == "uplink" {
   860  			uplinkif = gifs.Network.HREF
   861  		}
   862  	}
   863  
   864  	newEdgeConfig := egw.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration
   865  
   866  	snat := &types.NatRule{
   867  		Description: description,
   868  		RuleType:    "SNAT",
   869  		IsEnabled:   addrOf(true),
   870  		GatewayNatRule: &types.GatewayNatRule{
   871  			Interface: &types.Reference{
   872  				HREF: uplinkif,
   873  			},
   874  			OriginalIP:   internal,
   875  			TranslatedIP: external,
   876  			Protocol:     "any",
   877  		},
   878  	}
   879  
   880  	if newEdgeConfig.NatService == nil {
   881  		newEdgeConfig.NatService = &types.NatService{}
   882  	}
   883  	newEdgeConfig.NatService.NatRule = append(newEdgeConfig.NatService.NatRule, snat)
   884  
   885  	dnat := &types.NatRule{
   886  		Description: description,
   887  		RuleType:    "DNAT",
   888  		IsEnabled:   addrOf(true),
   889  		GatewayNatRule: &types.GatewayNatRule{
   890  			Interface: &types.Reference{
   891  				HREF: uplinkif,
   892  			},
   893  			OriginalIP:     external,
   894  			OriginalPort:   "any",
   895  			TranslatedIP:   internal,
   896  			TranslatedPort: "any",
   897  			Protocol:       "any",
   898  		},
   899  	}
   900  
   901  	newEdgeConfig.NatService.NatRule = append(newEdgeConfig.NatService.NatRule, dnat)
   902  
   903  	fwin := &types.FirewallRule{
   904  		Description: description,
   905  		IsEnabled:   true,
   906  		Policy:      "allow",
   907  		Protocols: &types.FirewallRuleProtocols{
   908  			Any: true,
   909  		},
   910  		DestinationPortRange: "Any",
   911  		DestinationIP:        external,
   912  		SourcePortRange:      "Any",
   913  		SourceIP:             "Any",
   914  		EnableLogging:        false,
   915  	}
   916  
   917  	newEdgeConfig.FirewallService.FirewallRule = append(newEdgeConfig.FirewallService.FirewallRule, fwin)
   918  
   919  	fwout := &types.FirewallRule{
   920  		Description: description,
   921  		IsEnabled:   true,
   922  		Policy:      "allow",
   923  		Protocols: &types.FirewallRuleProtocols{
   924  			Any: true,
   925  		},
   926  		DestinationPortRange: "Any",
   927  		DestinationIP:        "Any",
   928  		SourcePortRange:      "Any",
   929  		SourceIP:             internal,
   930  		EnableLogging:        false,
   931  	}
   932  
   933  	newEdgeConfig.FirewallService.FirewallRule = append(newEdgeConfig.FirewallService.FirewallRule, fwout)
   934  
   935  	apiEndpoint := urlParseRequestURI(egw.EdgeGateway.HREF)
   936  	apiEndpoint.Path += "/action/configureServices"
   937  
   938  	// Return the task
   939  	return egw.client.ExecuteTaskRequest(apiEndpoint.String(), http.MethodPost,
   940  		"application/vnd.vmware.admin.edgeGatewayServiceConfiguration+xml", "error reconfiguring Edge Gateway: %s", newEdgeConfig)
   941  
   942  }
   943  
   944  func (egw *EdgeGateway) AddIpsecVPN(ipsecVPNConfig *types.EdgeGatewayServiceConfiguration) (Task, error) {
   945  
   946  	err := egw.Refresh()
   947  	if err != nil {
   948  		fmt.Printf("error: %s\n", err)
   949  	}
   950  
   951  	ipsecVPNConfig.Xmlns = types.XMLNamespaceVCloud
   952  
   953  	apiEndpoint := urlParseRequestURI(egw.EdgeGateway.HREF)
   954  	apiEndpoint.Path += "/action/configureServices"
   955  
   956  	// Return the task
   957  	return egw.client.ExecuteTaskRequest(apiEndpoint.String(), http.MethodPost,
   958  		"application/vnd.vmware.admin.edgeGatewayServiceConfiguration+xml", "error reconfiguring Edge Gateway: %s", ipsecVPNConfig)
   959  
   960  }
   961  
   962  // Removes an Edge Gateway VPN, by passing an empty configuration
   963  func (egw *EdgeGateway) RemoveIpsecVPN() (Task, error) {
   964  	err := egw.Refresh()
   965  	if err != nil {
   966  		fmt.Printf("error: %s\n", err)
   967  	}
   968  	ipsecVPNConfig := &types.EdgeGatewayServiceConfiguration{
   969  		Xmlns: types.XMLNamespaceVCloud,
   970  		GatewayIpsecVpnService: &types.GatewayIpsecVpnService{
   971  			IsEnabled: false,
   972  		},
   973  	}
   974  	return egw.AddIpsecVPN(ipsecVPNConfig)
   975  }
   976  
   977  // Deletes the edge gateway, returning a task and an error with the operation result.
   978  // https://code.vmware.com/apis/442/vcloud-director/doc/doc/operations/DELETE-EdgeGateway.html
   979  func (egw *EdgeGateway) DeleteAsync(force bool, recursive bool) (Task, error) {
   980  	util.Logger.Printf("[TRACE] EdgeGateway.Delete - deleting edge gateway with force: %t, recursive: %t", force, recursive)
   981  
   982  	if egw.EdgeGateway.HREF == "" {
   983  		return Task{}, fmt.Errorf("cannot delete, HREF is missing")
   984  	}
   985  
   986  	egwUrl, err := url.ParseRequestURI(egw.EdgeGateway.HREF)
   987  	if err != nil {
   988  		return Task{}, fmt.Errorf("error parsing edge gateway url: %s", err)
   989  	}
   990  
   991  	req := egw.client.NewRequest(map[string]string{
   992  		"force":     strconv.FormatBool(force),
   993  		"recursive": strconv.FormatBool(recursive),
   994  	}, http.MethodDelete, *egwUrl, nil)
   995  	resp, err := checkResp(egw.client.Http.Do(req))
   996  	if err != nil {
   997  		return Task{}, fmt.Errorf("error deleting edge gateway: %s", err)
   998  	}
   999  	task := NewTask(egw.client)
  1000  	if err = decodeBody(types.BodyTypeXML, resp, task.Task); err != nil {
  1001  		return Task{}, fmt.Errorf("error decoding task response: %s", err)
  1002  	}
  1003  	return *task, err
  1004  }
  1005  
  1006  // Deletes the edge gateway, returning an error with the operation result.
  1007  // https://code.vmware.com/apis/442/vcloud-director/doc/doc/operations/DELETE-EdgeGateway.html
  1008  func (egw *EdgeGateway) Delete(force bool, recursive bool) error {
  1009  
  1010  	task, err := egw.DeleteAsync(force, recursive)
  1011  	if err != nil {
  1012  		return err
  1013  	}
  1014  	if task.Task.Status == "error" {
  1015  		return fmt.Errorf(combinedTaskErrorMessage(task.Task, fmt.Errorf("edge gateway not properly destroyed")))
  1016  	}
  1017  
  1018  	err = task.WaitTaskCompletion()
  1019  	if err != nil {
  1020  		return fmt.Errorf(combinedTaskErrorMessage(task.Task, err))
  1021  	}
  1022  
  1023  	return nil
  1024  }
  1025  
  1026  // GetNetworks returns the list of networks associated with an edge gateway
  1027  // In the return structure, an interfaceType of "uplink" indicates an external network,
  1028  // while "internal" is for Org VDC routed networks
  1029  func (egw *EdgeGateway) GetNetworks() ([]SimpleNetworkIdentifier, error) {
  1030  	var networks []SimpleNetworkIdentifier
  1031  	err := egw.Refresh()
  1032  	if err != nil {
  1033  		return networks, err
  1034  	}
  1035  	for _, net := range egw.EdgeGateway.Configuration.GatewayInterfaces.GatewayInterface {
  1036  		netIdentifier := SimpleNetworkIdentifier{
  1037  			Name:          net.Name,
  1038  			InterfaceType: net.InterfaceType,
  1039  		}
  1040  		networks = append(networks, netIdentifier)
  1041  	}
  1042  
  1043  	return networks, nil
  1044  }
  1045  
  1046  // HasDefaultGateway returns true if the edge gateway uses one of the external
  1047  // networks as default gateway
  1048  func (egw *EdgeGateway) HasDefaultGateway() bool {
  1049  	if egw.EdgeGateway.Configuration != nil &&
  1050  		egw.EdgeGateway.Configuration.GatewayInterfaces != nil {
  1051  		for _, gw := range egw.EdgeGateway.Configuration.GatewayInterfaces.GatewayInterface {
  1052  			// Check if the interface is used for default route
  1053  			if gw.UseForDefaultRoute {
  1054  				// Look for a specific subnet which is used as a default route
  1055  				for _, subnetParticipation := range gw.SubnetParticipation {
  1056  					if subnetParticipation.UseForDefaultRoute &&
  1057  						subnetParticipation.Gateway != "" &&
  1058  						subnetParticipation.Netmask != "" {
  1059  						return true
  1060  					}
  1061  				}
  1062  			}
  1063  		}
  1064  	}
  1065  	return false
  1066  }
  1067  
  1068  // HasAdvancedNetworking returns true if the edge gateway has advanced network configuration enabled
  1069  func (egw *EdgeGateway) HasAdvancedNetworking() bool {
  1070  	return egw.EdgeGateway.Configuration != nil &&
  1071  		egw.EdgeGateway.Configuration.AdvancedNetworkingEnabled != nil &&
  1072  		*egw.EdgeGateway.Configuration.AdvancedNetworkingEnabled
  1073  }
  1074  
  1075  // buildProxiedEdgeEndpointURL helps to get root endpoint for Edge Gateway using the
  1076  // NSX API Proxy and can append optionalSuffix which must have its own leading /
  1077  func (egw *EdgeGateway) buildProxiedEdgeEndpointURL(optionalSuffix string) (string, error) {
  1078  	apiEndpoint, err := url.ParseRequestURI(egw.EdgeGateway.HREF)
  1079  	if err != nil {
  1080  		return "", fmt.Errorf("unable to process edge gateway URL: %s", err)
  1081  	}
  1082  	edgeID := strings.Split(egw.EdgeGateway.ID, ":")
  1083  	if len(edgeID) != 4 {
  1084  		return "", fmt.Errorf("unable to find edge gateway id: %s", egw.EdgeGateway.ID)
  1085  	}
  1086  	hostname := apiEndpoint.Scheme + "://" + apiEndpoint.Host + "/network/edges/" + edgeID[3]
  1087  
  1088  	if optionalSuffix != "" {
  1089  		return hostname + optionalSuffix, nil
  1090  	}
  1091  
  1092  	return hostname, nil
  1093  }
  1094  
  1095  // GetLBGeneralParams retrieves load balancer configuration of `&types.LoadBalancer` and can be used
  1096  // to access global configuration options. These are 4 fields only:
  1097  // LoadBalancer.Enabled, LoadBalancer.AccelerationEnabled, LoadBalancer.Logging.Enable,
  1098  // LoadBalancer.Logging.LogLevel
  1099  func (egw *EdgeGateway) GetLBGeneralParams() (*types.LbGeneralParamsWithXml, error) {
  1100  	if !egw.HasAdvancedNetworking() {
  1101  		return nil, fmt.Errorf("only advanced edge gateway supports load balancing")
  1102  	}
  1103  
  1104  	httpPath, err := egw.buildProxiedEdgeEndpointURL(types.LbConfigPath)
  1105  	if err != nil {
  1106  		return nil, fmt.Errorf("could not get Edge Gateway API endpoint: %s", err)
  1107  	}
  1108  
  1109  	loadBalancerConfig := &types.LbGeneralParamsWithXml{}
  1110  	_, err = egw.client.ExecuteRequest(httpPath, http.MethodGet, types.AnyXMLMime,
  1111  		"unable to read load balancer configuration: %s", nil, loadBalancerConfig)
  1112  
  1113  	if err != nil {
  1114  		return nil, err
  1115  	}
  1116  
  1117  	return loadBalancerConfig, nil
  1118  }
  1119  
  1120  // UpdateLBGeneralParams allows to update global load balancer configuration.
  1121  // It accepts four fields (Enabled, AccelerationEnabled, Logging.Enable, Logging.LogLevel) and uses
  1122  // them to construct types.LbGeneralParamsWithXml without altering other options to prevent config
  1123  // corruption.
  1124  // They are represented in load balancer global configuration tab in the UI.
  1125  func (egw *EdgeGateway) UpdateLBGeneralParams(enabled, accelerationEnabled, loggingEnabled bool, logLevel string) (*types.LbGeneralParamsWithXml, error) {
  1126  	if !egw.HasAdvancedNetworking() {
  1127  		return nil, fmt.Errorf("only advanced edge gateway supports load balancing")
  1128  	}
  1129  
  1130  	if err := validateUpdateLBGeneralParams(logLevel); err != nil {
  1131  		return nil, err
  1132  	}
  1133  	// Retrieve load balancer to work on latest configuration
  1134  	currentLb, err := egw.GetLBGeneralParams()
  1135  	if err != nil {
  1136  		return nil, fmt.Errorf("unable to retrieve load balancer before update: %s", err)
  1137  	}
  1138  
  1139  	// Check if change is needed. If not - return early.
  1140  	if currentLb.Logging != nil &&
  1141  		currentLb.Enabled == enabled && currentLb.AccelerationEnabled == accelerationEnabled &&
  1142  		currentLb.Logging.Enable == loggingEnabled && currentLb.Logging.LogLevel == logLevel {
  1143  		return currentLb, nil
  1144  	}
  1145  
  1146  	// Modify only the global configuration settings
  1147  	currentLb.Enabled = enabled
  1148  	currentLb.AccelerationEnabled = accelerationEnabled
  1149  	currentLb.Logging = &types.LbLogging{
  1150  		Enable:   loggingEnabled,
  1151  		LogLevel: logLevel,
  1152  	}
  1153  	// Omit the version as it is updated automatically with each put
  1154  	currentLb.Version = ""
  1155  
  1156  	// Push updated configuration
  1157  	httpPath, err := egw.buildProxiedEdgeEndpointURL(types.LbConfigPath)
  1158  	if err != nil {
  1159  		return nil, fmt.Errorf("could not get Edge Gateway API endpoint: %s", err)
  1160  	}
  1161  	_, err = egw.client.ExecuteRequestWithCustomError(httpPath, http.MethodPut, types.AnyXMLMime,
  1162  		"error while updating load balancer config: %s", currentLb, &types.NSXError{})
  1163  	if err != nil {
  1164  		return nil, err
  1165  	}
  1166  
  1167  	// Retrieve configuration after update
  1168  	updatedLb, err := egw.GetLBGeneralParams()
  1169  	if err != nil {
  1170  		return nil, fmt.Errorf("unable to retrieve load balancer config after update: %s", err)
  1171  	}
  1172  
  1173  	return updatedLb, nil
  1174  }
  1175  
  1176  // GetFirewallConfig retrieves firewall configuration and can be used
  1177  // to alter main configuration options. These are 3 fields only:
  1178  // FirewallConfigWithXml.Enabled, FirewallConfigWithXml.DefaultPolicy.LoggingEnabled and
  1179  // FirewallConfigWithXml.DefaultPolicy.Action
  1180  func (egw *EdgeGateway) GetFirewallConfig() (*types.FirewallConfigWithXml, error) {
  1181  	if !egw.HasAdvancedNetworking() {
  1182  		return nil, fmt.Errorf("only advanced edge gateway support firewall configuration")
  1183  	}
  1184  
  1185  	httpPath, err := egw.buildProxiedEdgeEndpointURL(types.EdgeFirewallPath)
  1186  	if err != nil {
  1187  		return nil, fmt.Errorf("could not get Edge Gateway API endpoint: %s", err)
  1188  	}
  1189  
  1190  	firewallConfig := &types.FirewallConfigWithXml{}
  1191  	_, err = egw.client.ExecuteRequest(httpPath, http.MethodGet, types.AnyXMLMime,
  1192  		"unable to read firewall configuration: %s", nil, firewallConfig)
  1193  
  1194  	if err != nil {
  1195  		return nil, err
  1196  	}
  1197  
  1198  	return firewallConfig, nil
  1199  }
  1200  
  1201  // UpdateFirewallConfig allows to update firewall configuration.
  1202  // It accepts three fields (Enabled, DefaultLoggingEnabled, DefaultAction) and uses
  1203  // them to construct types.FirewallConfigWithXml without altering other options to prevent config
  1204  // corruption.
  1205  // They are represented in firewall configuration page in the UI.
  1206  func (egw *EdgeGateway) UpdateFirewallConfig(enabled, defaultLoggingEnabled bool, defaultAction string) (*types.FirewallConfigWithXml, error) {
  1207  	if !egw.HasAdvancedNetworking() {
  1208  		return nil, fmt.Errorf("only advanced edge gateway supports load balancing")
  1209  	}
  1210  
  1211  	if defaultAction != "accept" && defaultAction != "deny" {
  1212  		return nil, fmt.Errorf("default action must be either 'accept' or 'deny'")
  1213  	}
  1214  
  1215  	// Retrieve firewall latest configuration
  1216  	currentFw, err := egw.GetFirewallConfig()
  1217  	if err != nil {
  1218  		return nil, fmt.Errorf("unable to retrieve firewall config before update: %s", err)
  1219  	}
  1220  
  1221  	// Check if change is needed. If not - return early.
  1222  	if currentFw.Enabled == enabled && currentFw.DefaultPolicy.LoggingEnabled == defaultLoggingEnabled &&
  1223  		currentFw.DefaultPolicy.Action == defaultAction {
  1224  		return currentFw, nil
  1225  	}
  1226  
  1227  	// Modify only the global configuration settings
  1228  	currentFw.Enabled = enabled
  1229  	currentFw.DefaultPolicy.LoggingEnabled = defaultLoggingEnabled
  1230  	currentFw.DefaultPolicy.Action = defaultAction
  1231  
  1232  	// Omit the version as it is updated automatically with each put
  1233  	currentFw.Version = ""
  1234  
  1235  	// Push updated configuration
  1236  	httpPath, err := egw.buildProxiedEdgeEndpointURL(types.EdgeFirewallPath)
  1237  	if err != nil {
  1238  		return nil, fmt.Errorf("could not get Edge Gateway API endpoint: %s", err)
  1239  	}
  1240  	_, err = egw.client.ExecuteRequestWithCustomError(httpPath, http.MethodPut, types.AnyXMLMime,
  1241  		"error while updating firewall configuration : %s", currentFw, &types.NSXError{})
  1242  	if err != nil {
  1243  		return nil, err
  1244  	}
  1245  
  1246  	// Retrieve configuration after update
  1247  	updatedFw, err := egw.GetFirewallConfig()
  1248  	if err != nil {
  1249  		return nil, fmt.Errorf("unable to retrieve firewall after update: %s", err)
  1250  	}
  1251  
  1252  	return updatedFw, nil
  1253  }
  1254  
  1255  // validateUpdateLoadBalancer validates mandatory fields for global load balancer configuration
  1256  // settings
  1257  func validateUpdateLBGeneralParams(logLevel string) error {
  1258  	if logLevel == "" {
  1259  		return fmt.Errorf("field Logging.LogLevel must be set to update load balancer")
  1260  	}
  1261  
  1262  	return nil
  1263  }
  1264  
  1265  // getVdcNetworks retrieves a structure of type EdgeGatewayInterfaces which contains network
  1266  // interfaces available in Edge Gateway (uses "/vdcNetworks" endpoint)
  1267  func (egw *EdgeGateway) getVdcNetworks() (*types.EdgeGatewayInterfaces, error) {
  1268  	if !egw.HasAdvancedNetworking() {
  1269  		return nil, fmt.Errorf("only advanced edge gateway supports vNics")
  1270  	}
  1271  
  1272  	httpPath, err := egw.buildProxiedEdgeEndpointURL("/vdcNetworks")
  1273  	if err != nil {
  1274  		return nil, fmt.Errorf("could not get Edge Gateway API endpoint: %s", err)
  1275  	}
  1276  
  1277  	vnicConfig := &types.EdgeGatewayInterfaces{}
  1278  	_, err = egw.client.ExecuteRequest(httpPath, http.MethodGet, types.AnyXMLMime,
  1279  		"unable to edge gateway vnic configuration: %s", nil, vnicConfig)
  1280  
  1281  	if err != nil {
  1282  		return nil, err
  1283  	}
  1284  
  1285  	return vnicConfig, nil
  1286  }
  1287  
  1288  // GetVnicIndexByNetworkNameAndType returns *int of vNic index for specified network name and network type
  1289  // networkType one of: 'internal', 'uplink', 'trunk', 'subinterface'
  1290  // networkName cannot be empty
  1291  func (egw *EdgeGateway) GetVnicIndexByNetworkNameAndType(networkName, networkType string) (*int, error) {
  1292  	vnics, err := egw.getVdcNetworks()
  1293  	if err != nil {
  1294  		return nil, fmt.Errorf("cannot retrieve vNic configuration: %s", err)
  1295  	}
  1296  	return getVnicIndexByNetworkNameAndType(networkName, networkType, vnics)
  1297  }
  1298  
  1299  // GetAnyVnicIndexByNetworkName parses XML structure of vNic mapping to networks in edge gateway XML
  1300  // and returns *int of vNic index and network type by network name
  1301  // networkName cannot be empty
  1302  // networkType will be one of: 'internal', 'uplink', 'trunk', 'subinterface'
  1303  //
  1304  // Warning: this function assumes that there are no duplicate network names attached. If it is so
  1305  // this function will return the first network
  1306  func (egw *EdgeGateway) GetAnyVnicIndexByNetworkName(networkName string) (*int, string, error) {
  1307  	vnics, err := egw.getVdcNetworks()
  1308  	if err != nil {
  1309  		return nil, "", fmt.Errorf("cannot retrieve vNic configuration: %s", err)
  1310  	}
  1311  
  1312  	var foundVnicIndex *int
  1313  	var foundVnicType string
  1314  
  1315  	possibleNicTypes := []string{types.EdgeGatewayVnicTypeUplink, types.EdgeGatewayVnicTypeInternal,
  1316  		types.EdgeGatewayVnicTypeTrunk, types.EdgeGatewayVnicTypeSubinterface}
  1317  
  1318  	for _, nicType := range possibleNicTypes {
  1319  		vNicIndex, err := getVnicIndexByNetworkNameAndType(networkName, nicType, vnics)
  1320  		if err == nil { // nil error means we have found nic
  1321  			foundVnicIndex = vNicIndex
  1322  			foundVnicType = nicType
  1323  			break
  1324  		}
  1325  	}
  1326  
  1327  	if foundVnicIndex == nil && foundVnicType == "" {
  1328  		return nil, "", ErrorEntityNotFound
  1329  	}
  1330  	return foundVnicIndex, foundVnicType, nil
  1331  }
  1332  
  1333  // GetNetworkNameAndTypeByVnicIndex returns network name and network type for given vNic index
  1334  // returned networkType can be one of: 'internal', 'uplink', 'trunk', 'subinterface'
  1335  func (egw *EdgeGateway) GetNetworkNameAndTypeByVnicIndex(vNicIndex int) (string, string, error) {
  1336  	vnics, err := egw.getVdcNetworks()
  1337  	if err != nil {
  1338  		return "", "", fmt.Errorf("cannot retrieve vNic configuration: %s", err)
  1339  	}
  1340  	return getNetworkNameAndTypeByVnicIndex(vNicIndex, vnics)
  1341  }
  1342  
  1343  // getVnicIndexByNetworkNameAndType is wrapped and used by public function GetVnicIndexByNetworkNameAndType
  1344  func getVnicIndexByNetworkNameAndType(networkName, networkType string, vnics *types.EdgeGatewayInterfaces) (*int, error) {
  1345  	if networkName == "" {
  1346  		return nil, fmt.Errorf("network name cannot be empty")
  1347  	}
  1348  	if networkType != types.EdgeGatewayVnicTypeUplink &&
  1349  		networkType != types.EdgeGatewayVnicTypeInternal &&
  1350  		networkType != types.EdgeGatewayVnicTypeTrunk &&
  1351  		networkType != types.EdgeGatewayVnicTypeSubinterface {
  1352  		return nil, fmt.Errorf("networkType must be one of 'uplink', 'internal', 'trunk', 'subinterface'")
  1353  	}
  1354  
  1355  	var foundIndex *int
  1356  	foundCount := 0
  1357  
  1358  	for _, vnic := range vnics.EdgeInterface {
  1359  		// Look for matching portgroup name and network type. If the PortgroupName is not empty -
  1360  		// check that it contains network name as well.
  1361  		if vnic.Name == networkName && vnic.Type == networkType &&
  1362  			(vnic.PortgroupName == networkName || vnic.PortgroupName == "") {
  1363  			foundIndex = vnic.Index
  1364  			foundCount++
  1365  		}
  1366  	}
  1367  
  1368  	if foundCount > 1 {
  1369  		return nil, fmt.Errorf("more than one (%d) networks of type '%s' with name '%s' found",
  1370  			foundCount, networkType, networkName)
  1371  	}
  1372  
  1373  	if foundCount == 0 {
  1374  		return nil, ErrorEntityNotFound
  1375  	}
  1376  
  1377  	return foundIndex, nil
  1378  }
  1379  
  1380  // getNetworkNameAndTypeByVnicIndex looks up network type and name in list of edge gateway interfaces
  1381  func getNetworkNameAndTypeByVnicIndex(vNicIndex int, vnics *types.EdgeGatewayInterfaces) (string, string, error) {
  1382  	if vNicIndex < 0 {
  1383  		return "", "", fmt.Errorf("vNic index cannot be negative")
  1384  	}
  1385  
  1386  	foundCount := 0
  1387  	var networkName, networkType string
  1388  
  1389  	for _, vnic := range vnics.EdgeInterface {
  1390  		if vnic.Index != nil && *vnic.Index == vNicIndex {
  1391  			foundCount++
  1392  			networkName = vnic.Name
  1393  			networkType = vnic.Type
  1394  		}
  1395  	}
  1396  
  1397  	if foundCount > 1 {
  1398  		return "", "", fmt.Errorf("more than one networks found for vNic %d", vNicIndex)
  1399  	}
  1400  
  1401  	if foundCount == 0 {
  1402  		return "", "", ErrorEntityNotFound
  1403  	}
  1404  
  1405  	return networkName, networkType, nil
  1406  }
  1407  
  1408  // UpdateAsync updates the edge gateway in place with the information contained in the internal structure
  1409  func (egw *EdgeGateway) UpdateAsync() (Task, error) {
  1410  
  1411  	egw.EdgeGateway.Xmlns = types.XMLNamespaceVCloud
  1412  	egw.EdgeGateway.Configuration.Xmlns = types.XMLNamespaceVCloud
  1413  	egw.EdgeGateway.Tasks = nil
  1414  
  1415  	// Return the task
  1416  	return egw.client.ExecuteTaskRequest(egw.EdgeGateway.HREF, http.MethodPut,
  1417  		types.MimeEdgeGateway, "error updating Edge Gateway: %s", egw.EdgeGateway)
  1418  }
  1419  
  1420  // Update is a wrapper around UpdateAsync
  1421  // The pointer receiver is refreshed after update
  1422  func (egw *EdgeGateway) Update() error {
  1423  
  1424  	task, err := egw.UpdateAsync()
  1425  	if err != nil {
  1426  		return err
  1427  	}
  1428  	err = task.WaitTaskCompletion()
  1429  	if err != nil {
  1430  		return err
  1431  	}
  1432  	return egw.Refresh()
  1433  }