github.com/vmware/go-vcloud-director/v2@v2.24.0/govcd/orgvdcnetwork.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  	"encoding/xml"
    10  	"fmt"
    11  	"net/http"
    12  	"net/url"
    13  	"os"
    14  	"regexp"
    15  	"strings"
    16  	"time"
    17  
    18  	"github.com/vmware/go-vcloud-director/v2/types/v56"
    19  	"github.com/vmware/go-vcloud-director/v2/util"
    20  )
    21  
    22  // OrgVDCNetwork an org vdc network client
    23  type OrgVDCNetwork struct {
    24  	OrgVDCNetwork *types.OrgVDCNetwork
    25  	client        *Client
    26  }
    27  
    28  var reErrorBusy2 = regexp.MustCompile("is busy, cannot proceed with the operation.$")
    29  
    30  // NewOrgVDCNetwork creates an org vdc network client
    31  func NewOrgVDCNetwork(cli *Client) *OrgVDCNetwork {
    32  	return &OrgVDCNetwork{
    33  		OrgVDCNetwork: new(types.OrgVDCNetwork),
    34  		client:        cli,
    35  	}
    36  }
    37  
    38  func (orgVdcNet *OrgVDCNetwork) Refresh() error {
    39  	if orgVdcNet.OrgVDCNetwork.HREF == "" {
    40  		return fmt.Errorf("cannot refresh, Object is empty")
    41  	}
    42  
    43  	refreshUrl := orgVdcNet.OrgVDCNetwork.HREF
    44  
    45  	// Empty struct before a new unmarshal, otherwise we end up with duplicate
    46  	// elements in slices.
    47  	orgVdcNet.OrgVDCNetwork = &types.OrgVDCNetwork{}
    48  
    49  	_, err := orgVdcNet.client.ExecuteRequest(refreshUrl, http.MethodGet,
    50  		"", "error retrieving vDC network: %s", nil, orgVdcNet.OrgVDCNetwork)
    51  
    52  	return err
    53  }
    54  
    55  // Delete a network. Fails if the network is busy.
    56  // Returns a task to monitor the deletion.
    57  func (orgVdcNet *OrgVDCNetwork) Delete() (Task, error) {
    58  	err := orgVdcNet.Refresh()
    59  	if err != nil {
    60  		return Task{}, fmt.Errorf("error refreshing network: %s", err)
    61  	}
    62  	pathArr := strings.Split(orgVdcNet.OrgVDCNetwork.HREF, "/")
    63  	apiEndpoint := urlParseRequestURI(orgVdcNet.OrgVDCNetwork.HREF)
    64  	apiEndpoint.Path = "/api/admin/network/" + pathArr[len(pathArr)-1]
    65  
    66  	var resp *http.Response
    67  	for {
    68  		req := orgVdcNet.client.NewRequest(map[string]string{}, http.MethodDelete, *apiEndpoint, nil)
    69  		resp, err = checkResp(orgVdcNet.client.Http.Do(req))
    70  		if err != nil {
    71  			if reErrorBusy2.MatchString(err.Error()) {
    72  				time.Sleep(3 * time.Second)
    73  				continue
    74  			}
    75  			return Task{}, fmt.Errorf("error deleting Network: %s", err)
    76  		}
    77  		break
    78  	}
    79  
    80  	task := NewTask(orgVdcNet.client)
    81  
    82  	if err = decodeBody(types.BodyTypeXML, resp, task.Task); err != nil {
    83  		return Task{}, fmt.Errorf("error decoding Task response: %s", err)
    84  	}
    85  
    86  	// The request was successful
    87  	return *task, nil
    88  }
    89  
    90  // RemoveOrgVdcNetworkIfExists looks for an Org Vdc network and, if found, will delete it.
    91  func RemoveOrgVdcNetworkIfExists(vdc Vdc, networkName string) error {
    92  	network, err := vdc.GetOrgVdcNetworkByName(networkName, true)
    93  
    94  	if IsNotFound(err) {
    95  		// Network not found. No action needed
    96  		return nil
    97  	}
    98  	if err != nil {
    99  		// Some other error happened during retrieval. We pass it along
   100  		return err
   101  	}
   102  	// The network was found. We attempt deletion
   103  	task, err := network.Delete()
   104  	if err != nil {
   105  		return fmt.Errorf("error deleting network '%s' [phase 1]: %s", networkName, err)
   106  	}
   107  	err = task.WaitTaskCompletion()
   108  	if err != nil {
   109  		return fmt.Errorf("error deleting network '%s' [task]: %s", networkName, err)
   110  	}
   111  	return nil
   112  }
   113  
   114  // A wrapper call around CreateOrgVDCNetwork.
   115  // Creates a network and then uses the associated task to monitor its configuration
   116  func (vdc *Vdc) CreateOrgVDCNetworkWait(networkConfig *types.OrgVDCNetwork) error {
   117  
   118  	task, err := vdc.CreateOrgVDCNetwork(networkConfig)
   119  	if err != nil {
   120  		return fmt.Errorf("error creating the network: %s", err)
   121  	}
   122  	if task == (Task{}) {
   123  		return fmt.Errorf("NULL task retrieved after network creation")
   124  
   125  	}
   126  	err = task.WaitTaskCompletion()
   127  	// err = task.WaitInspectTaskCompletion(InspectTask, 10)
   128  	if err != nil {
   129  		return fmt.Errorf("error performing task: %s", err)
   130  	}
   131  	return nil
   132  }
   133  
   134  // Fine tuning network creation function.
   135  // Return an error (the result of the network creation) and a task (used to monitor
   136  // the network configuration)
   137  // This function can create any type of Org Vdc network. The exact type is determined by
   138  // the combination of properties given with the network configuration structure.
   139  func (vdc *Vdc) CreateOrgVDCNetwork(networkConfig *types.OrgVDCNetwork) (Task, error) {
   140  	for _, av := range vdc.Vdc.Link {
   141  		if av.Rel == "add" && av.Type == "application/vnd.vmware.vcloud.orgVdcNetwork+xml" {
   142  			createUrl, err := url.ParseRequestURI(av.HREF)
   143  
   144  			if err != nil {
   145  				return Task{}, fmt.Errorf("error decoding vdc response: %s", err)
   146  			}
   147  
   148  			networkConfig.Xmlns = types.XMLNamespaceVCloud
   149  
   150  			output, err := xml.MarshalIndent(networkConfig, "  ", "    ")
   151  			if err != nil {
   152  				return Task{}, fmt.Errorf("error marshaling OrgVDCNetwork compose: %s", err)
   153  			}
   154  
   155  			var resp *http.Response
   156  			for {
   157  				b := bytes.NewBufferString(xml.Header + string(output))
   158  				util.Logger.Printf("[DEBUG] VCD Client configuration: %s", b)
   159  				req := vdc.client.NewRequest(map[string]string{}, http.MethodPost, *createUrl, b)
   160  				req.Header.Add("Content-Type", av.Type)
   161  				resp, err = checkResp(vdc.client.Http.Do(req))
   162  				if err != nil {
   163  					if reErrorBusy2.MatchString(err.Error()) {
   164  						time.Sleep(3 * time.Second)
   165  						continue
   166  					}
   167  					return Task{}, fmt.Errorf("error instantiating a new OrgVDCNetwork: %s", err)
   168  				}
   169  				break
   170  			}
   171  			orgVDCNetwork := NewOrgVDCNetwork(vdc.client)
   172  			if err = decodeBody(types.BodyTypeXML, resp, orgVDCNetwork.OrgVDCNetwork); err != nil {
   173  				return Task{}, fmt.Errorf("error decoding orgvdcnetwork response: %s", err)
   174  			}
   175  			activeTasks := 0
   176  			// Makes sure that there is only one active task for this network.
   177  			for _, taskItem := range orgVDCNetwork.OrgVDCNetwork.Tasks.Task {
   178  				if taskItem.HREF != "" {
   179  					activeTasks += 1
   180  					if os.Getenv("GOVCD_DEBUG") != "" {
   181  						fmt.Printf("task %s (%s) is active\n", taskItem.HREF, taskItem.Status)
   182  					}
   183  				}
   184  			}
   185  			if activeTasks > 1 {
   186  				// By my understanding of the implementation, there should not be more than one task for this operation.
   187  				// If there is, we will need to change the logic of this function, as we can only return one task. (GM)
   188  				return Task{}, fmt.Errorf("found %d active tasks instead of one", activeTasks)
   189  			}
   190  			for _, taskItem := range orgVDCNetwork.OrgVDCNetwork.Tasks.Task {
   191  				return Task{taskItem, vdc.client}, nil
   192  			}
   193  			return Task{}, fmt.Errorf("[%s] no suitable task found", util.CurrentFuncName())
   194  		}
   195  	}
   196  	return Task{}, fmt.Errorf("network creation failed: no operational link found")
   197  }
   198  
   199  // GetNetworkList returns a list of networks for the VDC
   200  func (vdc *Vdc) GetNetworkList() ([]*types.QueryResultOrgVdcNetworkRecordType, error) {
   201  	// Find the list of networks with the wanted name
   202  	result, err := vdc.client.cumulativeQuery(types.QtOrgVdcNetwork, nil, map[string]string{
   203  		"type":          types.QtOrgVdcNetwork,
   204  		"filter":        fmt.Sprintf("vdc==%s", url.QueryEscape(vdc.Vdc.ID)),
   205  		"filterEncoded": "true",
   206  	})
   207  	if err != nil {
   208  		return nil, fmt.Errorf("[findEdgeGatewayConnection] error returning the list of networks for VDC: %s", err)
   209  	}
   210  	return result.Results.OrgVdcNetworkRecord, nil
   211  }
   212  
   213  // FindEdgeGatewayNameByNetwork searches the VDC for a connection between an edge gateway and a given network.
   214  // On success, returns the name of the edge gateway
   215  func (vdc *Vdc) FindEdgeGatewayNameByNetwork(networkName string) (string, error) {
   216  
   217  	// Find the list of networks with the wanted name
   218  	result, err := vdc.client.cumulativeQuery(types.QtOrgVdcNetwork, nil, map[string]string{
   219  		"type":          types.QtOrgVdcNetwork,
   220  		"filter":        fmt.Sprintf("name==%s;vdc==%s", url.QueryEscape(networkName), url.QueryEscape(vdc.Vdc.ID)),
   221  		"filterEncoded": "true",
   222  	})
   223  	if err != nil {
   224  		return "", fmt.Errorf("[findEdgeGatewayConnection] error returning the list of networks for VDC: %s", err)
   225  	}
   226  	netList := result.Results.OrgVdcNetworkRecord
   227  
   228  	for _, net := range netList {
   229  		if net.Name == networkName {
   230  			// linkType is not well documented, but empiric tests show that:
   231  			// 0 = direct
   232  			// 1 = routed
   233  			// 2 = isolated
   234  			if net.ConnectedTo != "" && net.LinkType == 1 { // We only want routed networks
   235  				return net.ConnectedTo, nil
   236  			}
   237  		}
   238  	}
   239  	return "", ErrorEntityNotFound
   240  }
   241  
   242  // getParentVdc retrieves the VDC to which the network is attached
   243  func (orgVdcNet *OrgVDCNetwork) getParentVdc() (*Vdc, error) {
   244  	for _, link := range orgVdcNet.OrgVDCNetwork.Link {
   245  		if link.Type == "application/vnd.vmware.vcloud.vdc+xml" {
   246  
   247  			vdc := NewVdc(orgVdcNet.client)
   248  
   249  			_, err := orgVdcNet.client.ExecuteRequest(link.HREF, http.MethodGet,
   250  				"", "error retrieving parent vdc: %s", nil, vdc.Vdc)
   251  			if err != nil {
   252  				return nil, err
   253  			}
   254  
   255  			return vdc, nil
   256  		}
   257  	}
   258  	return nil, fmt.Errorf("could not find a parent Vdc for network %s", orgVdcNet.OrgVDCNetwork.Name)
   259  }
   260  
   261  // getEdgeGateway retrieves the edge gateway connected to a routed network
   262  func (orgVdcNet *OrgVDCNetwork) getEdgeGateway() (*EdgeGateway, error) {
   263  	// If it is not routed, returns an error
   264  	if orgVdcNet.OrgVDCNetwork.Configuration.FenceMode != types.FenceModeNAT {
   265  		return nil, fmt.Errorf("network %s is not routed", orgVdcNet.OrgVDCNetwork.Name)
   266  	}
   267  	vdc, err := orgVdcNet.getParentVdc()
   268  	if err != nil {
   269  		return nil, err
   270  	}
   271  
   272  	// Since this function can be called from Update(), we must take into account the
   273  	// possibility of a name change. If this is happening, we need to retrieve the original
   274  	// name, which is still stored in the VDC.
   275  	oldNetwork, err := vdc.GetOrgVdcNetworkById(orgVdcNet.OrgVDCNetwork.ID, false)
   276  	if err != nil {
   277  		return nil, err
   278  	}
   279  	networkName := oldNetwork.OrgVDCNetwork.Name
   280  
   281  	edgeGatewayName, err := vdc.FindEdgeGatewayNameByNetwork(networkName)
   282  	if err != nil {
   283  		return nil, err
   284  	}
   285  
   286  	return vdc.GetEdgeGatewayByName(edgeGatewayName, false)
   287  }
   288  
   289  // UpdateAsync will change the contents of a network using the information in the
   290  // receiver data structure.
   291  func (orgVdcNet *OrgVDCNetwork) UpdateAsync() (Task, error) {
   292  	if orgVdcNet.OrgVDCNetwork.HREF == "" {
   293  		return Task{}, fmt.Errorf("cannot update Org VDC network: HREF is empty")
   294  	}
   295  	if orgVdcNet.OrgVDCNetwork.Name == "" {
   296  		return Task{}, fmt.Errorf("cannot update Org VDC network: name is empty")
   297  	}
   298  	if orgVdcNet.OrgVDCNetwork.Configuration == nil {
   299  		return Task{}, fmt.Errorf("cannot update Org VDC network: configuration is empty")
   300  	}
   301  
   302  	href := orgVdcNet.OrgVDCNetwork.HREF
   303  
   304  	if !strings.Contains(href, "/api/admin/") {
   305  		href = strings.ReplaceAll(href, "/api/", "/api/admin/")
   306  	}
   307  
   308  	// Routed networks need to have edge gateway information for both creation and update.
   309  	// Since the network data structure doesn't return edge gateway information,
   310  	// we fetch it explicitly.
   311  	if orgVdcNet.OrgVDCNetwork.Configuration.FenceMode == types.FenceModeNAT {
   312  		edgeGateway, err := orgVdcNet.getEdgeGateway()
   313  		if err != nil {
   314  			return Task{}, fmt.Errorf("error retrieving edge gateway for Org VDC network %s : %s", orgVdcNet.OrgVDCNetwork.Name, err)
   315  		}
   316  		orgVdcNet.OrgVDCNetwork.EdgeGateway = &types.Reference{
   317  			HREF: edgeGateway.EdgeGateway.HREF,
   318  			ID:   edgeGateway.EdgeGateway.ID,
   319  			Type: edgeGateway.EdgeGateway.Type,
   320  			Name: edgeGateway.EdgeGateway.Name,
   321  		}
   322  	}
   323  	return orgVdcNet.client.ExecuteTaskRequest(href, http.MethodPut,
   324  		types.MimeOrgVdcNetwork, "error updating Org VDC network: %s", orgVdcNet.OrgVDCNetwork)
   325  }
   326  
   327  // Update is a wrapper around UpdateAsync, where we
   328  // explicitly wait for the task to finish.
   329  // The pointer receiver is refreshed after update
   330  func (orgVdcNet *OrgVDCNetwork) Update() error {
   331  	task, err := orgVdcNet.UpdateAsync()
   332  	if err != nil {
   333  		return err
   334  	}
   335  	err = task.WaitTaskCompletion()
   336  	if err != nil {
   337  		return err
   338  	}
   339  
   340  	return orgVdcNet.Refresh()
   341  }
   342  
   343  // Rename is a wrapper around Update(), where we only change the name of the network
   344  // Since the purpose is explicitly changing the name, the function will fail if the new name
   345  // is not different from the existing one
   346  func (orgVdcNet *OrgVDCNetwork) Rename(newName string) error {
   347  	if orgVdcNet.OrgVDCNetwork.Name == newName {
   348  		return fmt.Errorf("new name is the same ase the existing name")
   349  	}
   350  	orgVdcNet.OrgVDCNetwork.Name = newName
   351  	return orgVdcNet.Update()
   352  }