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

     1  //go:build (lb || functional || integration || ALL) && !skipLong
     2  
     3  /*
     4   * Copyright 2019 VMware, Inc.  All rights reserved.  Licensed under the Apache v2 License.
     5   */
     6  
     7  package govcd
     8  
     9  import (
    10  	"fmt"
    11  	"io"
    12  	"net/http"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/vmware/go-vcloud-director/v2/types/v56"
    17  	. "gopkg.in/check.v1"
    18  )
    19  
    20  // Test_LB load balancer integration test
    21  // 1. Validates that all needed parameters are here
    22  // 2. Uploads or reuses media.photonOsOvaPath OVA image
    23  // 3. Creates RAW vApp and attaches vDC network to it
    24  // 4. Spawns two VMs with configuration script to server HTTP traffic
    25  // 5. Sets up load balancer
    26  // 6. Probes load balancer virtual server's external IP (edge gateway IP) for traffic
    27  // being server in 2 VMs
    28  // 7. Tears down
    29  func (vcd *TestVCD) Test_LB(check *C) {
    30  
    31  	// Validate prerequisites
    32  	validateTestLbPrerequisites(vcd, check)
    33  
    34  	vdc, edge, vappTemplate, vapp, desiredNetConfig, err := vcd.createAndGetResourcesForVmCreation(check, TestLb)
    35  	check.Assert(err, IsNil)
    36  
    37  	// The script below creates a file /tmp/node/server with single value `name` being set in it.
    38  	// It also disables iptables and spawns simple Python 3 HTTP server listening on port 8000
    39  	// in background which serves the just created `server` file.
    40  	vm1CustomizationScript := "mkdir /tmp/node && cd /tmp/node && echo -n 'FirstNode' > server && " +
    41  		"/bin/systemctl stop iptables && /usr/bin/python3 -m http.server 8000 &"
    42  	vm2CustomizationScript := "mkdir /tmp/node && cd /tmp/node && echo -n 'SecondNode' > server && " +
    43  		"/bin/systemctl stop iptables && /usr/bin/python3 -m http.server 8000 &"
    44  
    45  	vm1, err := spawnVM("FirstNode", 512, *vdc, *vapp, desiredNetConfig, vappTemplate, check, vm1CustomizationScript, true)
    46  	check.Assert(err, IsNil)
    47  	vm2, err := spawnVM("SecondNode", 512, *vdc, *vapp, desiredNetConfig, vappTemplate, check, vm2CustomizationScript, true)
    48  	check.Assert(err, IsNil)
    49  
    50  	// Get IPs allocated to the VMs
    51  	ip1 := vm1.VM.NetworkConnectionSection.NetworkConnection[0].IPAddress
    52  	ip2 := vm2.VM.NetworkConnectionSection.NetworkConnection[0].IPAddress
    53  
    54  	fmt.Printf("# VM '%s' got IP '%s' in vDC network %s\n", vm1.VM.Name, ip1, vcd.config.VCD.Network.Net1)
    55  	fmt.Printf("# VM '%s' got IP '%s' in vDC network %s\n", vm2.VM.Name, ip2, vcd.config.VCD.Network.Net1)
    56  
    57  	fmt.Printf("# Setting up load balancer for VMs: '%s' (%s), '%s' (%s)\n", vm1.VM.Name, ip1, vm2.VM.Name, ip2)
    58  
    59  	fmt.Printf("# Creating firewall rule for load balancer virtual server access. ")
    60  	ruleDescription := addFirewallRule(*vdc, vcd, check)
    61  	fmt.Printf("Done\n")
    62  
    63  	// Build load balancer
    64  	buildLb(*edge, ip1, ip2, vcd, check)
    65  
    66  	// Cache current load balancer settings for change validation in the end
    67  	beforeLb, beforeLbXml := testCacheLoadBalancer(*edge, check)
    68  
    69  	// Enable load balancer globally
    70  	fmt.Printf("# Enabling load balancer with acceleration: ")
    71  	_, err = edge.UpdateLBGeneralParams(true, true, true, "warning")
    72  	check.Assert(err, IsNil)
    73  	fmt.Printf("Done\n")
    74  
    75  	// Using external edge gateway IP for
    76  	queryUrl := "http://" + vcd.config.VCD.ExternalIp + ":8000/server"
    77  	fmt.Printf("# Querying load balancer for expected responses at %s\n", queryUrl)
    78  	queryErr := checkLb(queryUrl, []string{vm1.VM.Name, vm2.VM.Name}, vapp.client.MaxRetryTimeout)
    79  
    80  	// Remove firewall rule
    81  	fmt.Printf("# Deleting firewall rule used for load balancer virtual server access. ")
    82  	deleteFirewallRule(ruleDescription, *vdc, vcd, check)
    83  	fmt.Printf("Done\n")
    84  
    85  	// Restore global load balancer configuration
    86  	fmt.Printf("# Restoring load balancer global configuration: ")
    87  	_, err = edge.UpdateLBGeneralParams(beforeLb.Enabled, beforeLb.AccelerationEnabled,
    88  		beforeLb.Logging.Enable, beforeLb.Logging.LogLevel)
    89  	check.Assert(err, IsNil)
    90  	fmt.Printf("Done\n")
    91  
    92  	// Validate load balancer configuration against initially cached version
    93  	fmt.Printf("# Validating load balancer XML structure: ")
    94  	testCheckLoadBalancerConfig(beforeLb, beforeLbXml, *edge, check)
    95  	fmt.Printf("Done\n")
    96  
    97  	// Finally after some cleanups - check if querying succeeded
    98  	check.Assert(queryErr, IsNil)
    99  }
   100  
   101  // validateTestLbPrerequisites verifies the following:
   102  // * Edge Gateway is set in config
   103  // * ExternalIp is set in config (will be edge gateway external IP)
   104  // * PhotonOsOvaPath is set (will be used for spawning VMs)
   105  // * Edge Gateway can be found and it has advanced networking enabled (a must for load balancers)
   106  func validateTestLbPrerequisites(vcd *TestVCD, check *C) {
   107  	if vcd.config.VCD.EdgeGateway == "" {
   108  		check.Skip("Skipping test because no edge gateway given")
   109  	}
   110  
   111  	if vcd.config.VCD.ExternalIp == "" {
   112  		check.Skip("Skipping test because no edge gateway external IP given")
   113  	}
   114  
   115  	edge, err := vcd.vdc.GetEdgeGatewayByName(vcd.config.VCD.EdgeGateway, false)
   116  	check.Assert(err, IsNil)
   117  	check.Assert(edge.EdgeGateway.Name, Equals, vcd.config.VCD.EdgeGateway)
   118  
   119  	if !edge.HasAdvancedNetworking() {
   120  		check.Skip("Skipping test because the edge gateway does not have advanced networking enabled")
   121  	}
   122  
   123  }
   124  
   125  // buildLB establishes an HTTP load balancer for 2 IPs specified as arguments
   126  func buildLb(edge EdgeGateway, node1Ip, node2Ip string, vcd *TestVCD, check *C) {
   127  
   128  	_, serverPoolId, appProfileId, _ := buildTestLBVirtualServerPrereqs(node1Ip, node2Ip, TestLb,
   129  		check, vcd, edge)
   130  
   131  	// Configure creation object including reference to service monitor
   132  	lbVirtualServerConfig := &types.LbVirtualServer{
   133  		Name: TestLb,
   134  		// Load balancer virtual server serves on Edge gw IP
   135  		IpAddress:            vcd.config.VCD.ExternalIp,
   136  		Enabled:              true,
   137  		AccelerationEnabled:  true,
   138  		Protocol:             "http",
   139  		Port:                 8000,
   140  		ConnectionLimit:      5,
   141  		ConnectionRateLimit:  10,
   142  		ApplicationProfileId: appProfileId,
   143  		DefaultPoolId:        serverPoolId,
   144  	}
   145  
   146  	err := deleteLbVirtualServerIfExists(edge, lbVirtualServerConfig.Name)
   147  	check.Assert(err, IsNil)
   148  
   149  	_, err = edge.CreateLbVirtualServer(lbVirtualServerConfig)
   150  	check.Assert(err, IsNil)
   151  
   152  	// We created virtual server successfully therefore let's prepend it to cleanup list so that it
   153  	// is deleted before the child components
   154  	parentEntity := vcd.org.Org.Name + "|" + vcd.vdc.Vdc.Name + "|" + vcd.config.VCD.EdgeGateway
   155  	PrependToCleanupList(TestLb, "lbVirtualServer", parentEntity, check.TestName())
   156  }
   157  
   158  // checkLb queries specified endpoint until it gets all responses in expectedResponses slice
   159  func checkLb(queryUrl string, expectedResponses []string, maxRetryTimeout int) error {
   160  	var err error
   161  	if len(expectedResponses) == 0 {
   162  		return fmt.Errorf("no expected responses specified")
   163  	}
   164  
   165  	retryTimeout := maxRetryTimeout
   166  	// due to the VMs taking long time to boot it needs to be at least 5 minutes
   167  	// may be even more in slower environments
   168  	if maxRetryTimeout < 5*60 { // 5 minutes
   169  		retryTimeout = 5 * 60 // 5 minutes
   170  	}
   171  
   172  	timeOutAfterInterval := time.Duration(retryTimeout) * time.Second
   173  	timeoutAfter := time.After(timeOutAfterInterval)
   174  	tick := time.NewTicker(time.Duration(5) * time.Second)
   175  
   176  	httpClient := &http.Client{Timeout: 5 * time.Second}
   177  
   178  	fmt.Printf("# Waiting for the virtual server to accept responses (timeout after %s)"+
   179  		"\n[_ = timeout, x = connection refused, ?(err) = unknown error, / = no nodes are up yet, "+
   180  		". = no response from all nodes yet]: ", timeOutAfterInterval.String())
   181  
   182  	for {
   183  		select {
   184  		case <-timeoutAfter:
   185  			return fmt.Errorf("timed out waiting for all nodes to be up")
   186  		case <-tick.C:
   187  			var resp *http.Response
   188  			resp, err = httpClient.Get(queryUrl)
   189  			if err != nil {
   190  				switch {
   191  				case strings.Contains(err.Error(), "i/o timeout"):
   192  					fmt.Printf("_")
   193  				case strings.Contains(err.Error(), "connect: connection refused"):
   194  					fmt.Printf("x")
   195  				case strings.Contains(err.Error(), "connect: network is unreachable"):
   196  					fmt.Printf("/")
   197  				default:
   198  					fmt.Printf("?(%s)", err.Error())
   199  				}
   200  			}
   201  
   202  			if err == nil {
   203  				fmt.Printf(".") // progress bar when waiting for responses from all nodes
   204  				body, _ := io.ReadAll(resp.Body)
   205  				err = resp.Body.Close()
   206  				if err != nil {
   207  					return err
   208  				}
   209  				// check if the element is in the list
   210  				for index, value := range expectedResponses {
   211  					if value == string(body) {
   212  						expectedResponses = append(expectedResponses[:index], expectedResponses[index+1:]...)
   213  						if len(expectedResponses) > 0 {
   214  							fmt.Printf("\n# '%s' responded. Waiting for node(s) '%s': ",
   215  								value, strings.Join(expectedResponses, ","))
   216  						} else {
   217  							fmt.Printf("\n# Last node '%s' responded. Exiting\n", value)
   218  							return nil
   219  						}
   220  					}
   221  				}
   222  			}
   223  		}
   224  	}
   225  
   226  }
   227  
   228  // addFirewallRule adds a firewall rule needed to access virtual server port on edge gateway
   229  func addFirewallRule(vdc Vdc, vcd *TestVCD, check *C) string {
   230  	description := "Created by: " + TestLb
   231  
   232  	edge, err := vdc.GetEdgeGatewayByName(vcd.config.VCD.EdgeGateway, false)
   233  	check.Assert(err, IsNil)
   234  
   235  	// Open up firewall to access edge gateway on load balancer port
   236  	fwRule := &types.FirewallRule{
   237  		IsEnabled:     true,
   238  		Description:   description,
   239  		Protocols:     &types.FirewallRuleProtocols{TCP: true},
   240  		Port:          8000,
   241  		DestinationIP: vcd.config.VCD.ExternalIp,
   242  		SourceIP:      "any",
   243  		SourcePort:    -1,
   244  	}
   245  	fwRules := []*types.FirewallRule{fwRule}
   246  	task, err := edge.CreateFirewallRules("allow", fwRules)
   247  	check.Assert(err, IsNil)
   248  	err = task.WaitTaskCompletion()
   249  	check.Assert(err, IsNil)
   250  
   251  	return description
   252  }
   253  
   254  // deleteFirewallRule removes firewall rule which was used for testing load balancer
   255  func deleteFirewallRule(ruleDescription string, vdc Vdc, vcd *TestVCD, check *C) {
   256  	edge, err := vdc.GetEdgeGatewayByName(vcd.config.VCD.EdgeGateway, false)
   257  	check.Assert(err, IsNil)
   258  	rules := edge.EdgeGateway.Configuration.EdgeGatewayServiceConfiguration.FirewallService.FirewallRule
   259  	for index := range rules {
   260  		if rules[index].Description == ruleDescription {
   261  			rules = append(rules[:index], rules[index+1:]...)
   262  		}
   263  	}
   264  
   265  	task, err := edge.CreateFirewallRules("allow", rules)
   266  	check.Assert(err, IsNil)
   267  	err = task.WaitTaskCompletion()
   268  	check.Assert(err, IsNil)
   269  }