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 }