blob: 8191984ca5759364075e08bd886f10c67b309e08 [file] [log] [blame]
Elena Ezhovaa5105e62013-11-26 20:46:52 +04001# Copyright 2014 Mirantis.inc
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
Elena Ezhova6e73f462014-04-18 17:38:13 +040016
Elena Ezhova6e73f462014-04-18 17:38:13 +040017import tempfile
Elena Ezhovaa5105e62013-11-26 20:46:52 +040018import time
Elena Ezhova6e73f462014-04-18 17:38:13 +040019import urllib2
Elena Ezhovaa5105e62013-11-26 20:46:52 +040020
21from tempest.api.network import common as net_common
Elena Ezhova6e73f462014-04-18 17:38:13 +040022from tempest.common import commands
Elena Ezhovaa5105e62013-11-26 20:46:52 +040023from tempest import config
24from tempest import exceptions
25from tempest.scenario import manager
26from tempest import test
27
28config = config.CONF
29
30
31class TestLoadBalancerBasic(manager.NetworkScenarioTest):
32
33 """
34 This test checks basic load balancing.
35
36 The following is the scenario outline:
37 1. Create an instance
38 2. SSH to the instance and start two servers
39 3. Create a load balancer with two members and with ROUND_ROBIN algorithm
40 associate the VIP with a floating ip
41 4. Send 10 requests to the floating ip and check that they are shared
42 between the two servers and that both of them get equal portions
43 of the requests
44 """
45
46 @classmethod
47 def check_preconditions(cls):
48 super(TestLoadBalancerBasic, cls).check_preconditions()
49 cfg = config.network
50 if not test.is_extension_enabled('lbaas', 'network'):
51 msg = 'LBaaS Extension is not enabled'
52 cls.enabled = False
53 raise cls.skipException(msg)
54 if not (cfg.tenant_networks_reachable or cfg.public_network_id):
55 msg = ('Either tenant_networks_reachable must be "true", or '
56 'public_network_id must be defined.')
57 cls.enabled = False
58 raise cls.skipException(msg)
59
60 @classmethod
61 def setUpClass(cls):
62 super(TestLoadBalancerBasic, cls).setUpClass()
63 cls.check_preconditions()
Elena Ezhovaa5105e62013-11-26 20:46:52 +040064 cls.servers_keypairs = {}
Elena Ezhovaa5105e62013-11-26 20:46:52 +040065 cls.members = []
Elena Ezhovaa5105e62013-11-26 20:46:52 +040066 cls.floating_ips = {}
Elena Ezhova4a27b462014-04-09 15:25:46 +040067 cls.server_ips = {}
Elena Ezhovaa5105e62013-11-26 20:46:52 +040068 cls.port1 = 80
69 cls.port2 = 88
70
Elena Ezhova4a27b462014-04-09 15:25:46 +040071 def setUp(self):
72 super(TestLoadBalancerBasic, self).setUp()
73 self.server_ips = {}
Darragh O'Reilly7c8176e2014-04-26 16:03:23 +000074 self.server_fixed_ips = {}
Elena Ezhova4a27b462014-04-09 15:25:46 +040075 self._create_security_group()
76
Elena Ezhova4a27b462014-04-09 15:25:46 +040077 def _create_security_group(self):
78 self.security_group = self._create_security_group_neutron(
79 tenant_id=self.tenant_id)
Eugene Nikanorov55d13142014-03-24 15:39:21 +040080 self._create_security_group_rules_for_port(self.port1)
81 self._create_security_group_rules_for_port(self.port2)
82
83 def _create_security_group_rules_for_port(self, port):
84 rule = {
85 'direction': 'ingress',
86 'protocol': 'tcp',
87 'port_range_min': port,
88 'port_range_max': port,
89 }
90 self._create_security_group_rule(
91 client=self.network_client,
Elena Ezhova4a27b462014-04-09 15:25:46 +040092 secgroup=self.security_group,
Eugene Nikanorov55d13142014-03-24 15:39:21 +040093 tenant_id=self.tenant_id,
94 **rule)
Elena Ezhovaa5105e62013-11-26 20:46:52 +040095
Elena Ezhova4a27b462014-04-09 15:25:46 +040096 def _create_server(self, name):
Elena Ezhovaa5105e62013-11-26 20:46:52 +040097 keypair = self.create_keypair(name='keypair-%s' % name)
Elena Ezhova4a27b462014-04-09 15:25:46 +040098 security_groups = [self.security_group.name]
Elena Ezhova91531102014-02-07 17:25:58 +040099 net = self._list_networks(tenant_id=self.tenant_id)[0]
Yair Fried2d2f3fe2014-02-24 16:19:20 +0200100 create_kwargs = {
101 'nics': [
Elena Ezhova91531102014-02-07 17:25:58 +0400102 {'net-id': net['id']},
Yair Fried2d2f3fe2014-02-24 16:19:20 +0200103 ],
104 'key_name': keypair.name,
105 'security_groups': security_groups,
106 }
107 server = self.create_server(name=name,
108 create_kwargs=create_kwargs)
Elena Ezhova4a27b462014-04-09 15:25:46 +0400109 self.servers_keypairs[server.id] = keypair
Elena Ezhova91531102014-02-07 17:25:58 +0400110 if (config.network.public_network_id and not
111 config.network.tenant_networks_reachable):
112 public_network_id = config.network.public_network_id
113 floating_ip = self._create_floating_ip(
114 server, public_network_id)
115 self.floating_ips[floating_ip] = server
Elena Ezhova4a27b462014-04-09 15:25:46 +0400116 self.server_ips[server.id] = floating_ip.floating_ip_address
Elena Ezhova91531102014-02-07 17:25:58 +0400117 else:
Zhi Kun Liucd2e3772014-04-15 21:18:18 -0500118 self.server_ips[server.id] = server.networks[net['name']][0]
Darragh O'Reilly7c8176e2014-04-26 16:03:23 +0000119 self.server_fixed_ips[server.id] = server.networks[net['name']][0]
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400120 self.assertTrue(self.servers_keypairs)
Elena Ezhova91531102014-02-07 17:25:58 +0400121 return server
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400122
Elena Ezhova4a27b462014-04-09 15:25:46 +0400123 def _create_servers(self):
124 for count in range(2):
125 self._create_server(name=("server%s" % (count + 1)))
126 self.assertEqual(len(self.servers_keypairs), 2)
127
128 def _start_servers(self):
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400129 """
Elena Ezhova4a27b462014-04-09 15:25:46 +0400130 Start two backends
131
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400132 1. SSH to the instance
Elena Ezhova91531102014-02-07 17:25:58 +0400133 2. Start two http backends listening on ports 80 and 88 respectively
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400134 """
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400135
Elena Ezhova4a27b462014-04-09 15:25:46 +0400136 for server_id, ip in self.server_ips.iteritems():
137 private_key = self.servers_keypairs[server_id].private_key
138 server_name = self.compute_client.servers.get(server_id).name
Elena Ezhova6e73f462014-04-18 17:38:13 +0400139 username = config.scenario.ssh_user
Elena Ezhova4a27b462014-04-09 15:25:46 +0400140 ssh_client = self.get_remote_client(
141 server_or_ip=ip,
142 private_key=private_key)
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400143
Robert Mizielskie1d88992014-07-15 15:28:09 +0200144 # Write a backend's response into a file
Elena Ezhova0c8e3292014-06-05 12:15:39 +0400145 resp = """echo -ne "HTTP/1.1 200 OK\r\nContent-Length: 7\r\n""" \
146 """Connection: close\r\nContent-Type: text/html; """ \
147 """charset=UTF-8\r\n\r\n%s"; cat >/dev/null"""
148
Elena Ezhova6e73f462014-04-18 17:38:13 +0400149 with tempfile.NamedTemporaryFile() as script:
150 script.write(resp % server_name)
151 script.flush()
152 with tempfile.NamedTemporaryFile() as key:
153 key.write(private_key)
154 key.flush()
155 commands.copy_file_to_host(script.name,
Elena Ezhova0c8e3292014-06-05 12:15:39 +0400156 "/tmp/script1",
Elena Ezhova6e73f462014-04-18 17:38:13 +0400157 ip,
158 username, key.name)
Elena Ezhova0c8e3292014-06-05 12:15:39 +0400159
Elena Ezhova6e73f462014-04-18 17:38:13 +0400160 # Start netcat
Elena Ezhova0c8e3292014-06-05 12:15:39 +0400161 start_server = """sudo nc -ll -p %(port)s -e sh """ \
162 """/tmp/%(script)s &"""
Elena Ezhova6e73f462014-04-18 17:38:13 +0400163 cmd = start_server % {'port': self.port1,
164 'script': 'script1'}
Elena Ezhova4a27b462014-04-09 15:25:46 +0400165 ssh_client.exec_command(cmd)
Elena Ezhova0c8e3292014-06-05 12:15:39 +0400166
Elena Ezhova4a27b462014-04-09 15:25:46 +0400167 if len(self.server_ips) == 1:
Elena Ezhova6e73f462014-04-18 17:38:13 +0400168 with tempfile.NamedTemporaryFile() as script:
169 script.write(resp % 'server2')
170 script.flush()
171 with tempfile.NamedTemporaryFile() as key:
172 key.write(private_key)
173 key.flush()
174 commands.copy_file_to_host(script.name,
Elena Ezhova0c8e3292014-06-05 12:15:39 +0400175 "/tmp/script2", ip,
Elena Ezhova6e73f462014-04-18 17:38:13 +0400176 username, key.name)
177 cmd = start_server % {'port': self.port2,
178 'script': 'script2'}
Elena Ezhova4a27b462014-04-09 15:25:46 +0400179 ssh_client.exec_command(cmd)
Elena Ezhova4a27b462014-04-09 15:25:46 +0400180
181 def _check_connection(self, check_ip, port=80):
182 def try_connect(ip, port):
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400183 try:
Elena Ezhova6e73f462014-04-18 17:38:13 +0400184 resp = urllib2.urlopen("http://{0}:{1}/".format(ip, port))
Elena Ezhova4a27b462014-04-09 15:25:46 +0400185 if resp.getcode() == 200:
186 return True
187 return False
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400188 except IOError:
189 return False
190 timeout = config.compute.ping_timeout
Elena Ezhova4a27b462014-04-09 15:25:46 +0400191 start = time.time()
192 while not try_connect(check_ip, port):
193 if (time.time() - start) > timeout:
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400194 message = "Timed out trying to connect to %s" % check_ip
195 raise exceptions.TimeoutException(message)
196
197 def _create_pool(self):
198 """Create a pool with ROUND_ROBIN algorithm."""
Yair Fried2d2f3fe2014-02-24 16:19:20 +0200199 # get tenant subnet and verify there's only one
200 subnet = self._list_subnets(tenant_id=self.tenant_id)[0]
201 self.subnet = net_common.DeletableSubnet(client=self.network_client,
Elena Ezhova91531102014-02-07 17:25:58 +0400202 **subnet)
Yair Fried2d2f3fe2014-02-24 16:19:20 +0200203 self.pool = super(TestLoadBalancerBasic, self)._create_pool(
Elena Ezhova4a27b462014-04-09 15:25:46 +0400204 lb_method='ROUND_ROBIN',
205 protocol='HTTP',
206 subnet_id=self.subnet.id)
Yair Fried2d2f3fe2014-02-24 16:19:20 +0200207 self.assertTrue(self.pool)
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400208
Elena Ezhova4a27b462014-04-09 15:25:46 +0400209 def _create_members(self):
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400210 """
211 Create two members.
212
213 In case there is only one server, create both members with the same ip
214 but with different ports to listen on.
215 """
Elena Ezhova4a27b462014-04-09 15:25:46 +0400216
Darragh O'Reilly7c8176e2014-04-26 16:03:23 +0000217 for server_id, ip in self.server_fixed_ips.iteritems():
218 if len(self.server_fixed_ips) == 1:
Elena Ezhova4a27b462014-04-09 15:25:46 +0400219 member1 = self._create_member(address=ip,
220 protocol_port=self.port1,
221 pool_id=self.pool.id)
Elena Ezhova4a27b462014-04-09 15:25:46 +0400222 member2 = self._create_member(address=ip,
223 protocol_port=self.port2,
224 pool_id=self.pool.id)
Elena Ezhova4a27b462014-04-09 15:25:46 +0400225 self.members.extend([member1, member2])
226 else:
227 member = self._create_member(address=ip,
228 protocol_port=self.port1,
229 pool_id=self.pool.id)
Elena Ezhova4a27b462014-04-09 15:25:46 +0400230 self.members.append(member)
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400231 self.assertTrue(self.members)
232
233 def _assign_floating_ip_to_vip(self, vip):
234 public_network_id = config.network.public_network_id
Yair Fried2d2f3fe2014-02-24 16:19:20 +0200235 port_id = vip.port_id
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200236 floating_ip = self._create_floating_ip(vip, public_network_id,
237 port_id=port_id)
Yair Fried2d2f3fe2014-02-24 16:19:20 +0200238 self.floating_ips.setdefault(vip.id, [])
239 self.floating_ips[vip.id].append(floating_ip)
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400240
241 def _create_load_balancer(self):
242 self._create_pool()
Elena Ezhova4a27b462014-04-09 15:25:46 +0400243 self._create_members()
244 self.vip = self._create_vip(protocol='HTTP',
245 protocol_port=80,
246 subnet_id=self.subnet.id,
247 pool_id=self.pool.id)
Elena Ezhova4a27b462014-04-09 15:25:46 +0400248 self.status_timeout(NeutronRetriever(self.network_client,
249 self.network_client.vip_path,
250 net_common.DeletableVip),
251 self.vip.id,
252 expected_status='ACTIVE')
Elena Ezhova91531102014-02-07 17:25:58 +0400253 if (config.network.public_network_id and not
254 config.network.tenant_networks_reachable):
255 self._assign_floating_ip_to_vip(self.vip)
256 self.vip_ip = self.floating_ips[
257 self.vip.id][0]['floating_ip_address']
258 else:
259 self.vip_ip = self.vip.address
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400260
Darragh O'Reilly44fc88a2014-04-26 15:42:47 +0000261 # Currently the ovs-agent is not enforcing security groups on the
262 # vip port - see https://bugs.launchpad.net/neutron/+bug/1163569
263 # However the linuxbridge-agent does, and it is necessary to add a
264 # security group with a rule that allows tcp port 80 to the vip port.
265 body = {'port': {'security_groups': [self.security_group.id]}}
266 self.network_client.update_port(self.vip.port_id, body)
267
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400268 def _check_load_balancing(self):
269 """
Elena Ezhova6e73f462014-04-18 17:38:13 +0400270 1. Send 10 requests on the floating ip associated with the VIP
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400271 2. Check that the requests are shared between
272 the two servers and that both of them get equal portions
273 of the requests
274 """
275
Elena Ezhova91531102014-02-07 17:25:58 +0400276 self._check_connection(self.vip_ip)
Elena Ezhova6e73f462014-04-18 17:38:13 +0400277 self._send_requests(self.vip_ip, set(["server1", "server2"]))
Elena Ezhova4a27b462014-04-09 15:25:46 +0400278
Elena Ezhova6e73f462014-04-18 17:38:13 +0400279 def _send_requests(self, vip_ip, expected, num_req=10):
280 count = 0
281 while count < num_req:
Elena Ezhova0c8e3292014-06-05 12:15:39 +0400282 resp = []
283 for i in range(len(self.members)):
284 resp.append(
285 urllib2.urlopen(
286 "http://{0}/".format(vip_ip)).read())
287 count += 1
288 self.assertEqual(expected,
289 set(resp))
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400290
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400291 @test.attr(type='smoke')
292 @test.services('compute', 'network')
293 def test_load_balancer_basic(self):
Elena Ezhova4a27b462014-04-09 15:25:46 +0400294 self._create_server('server1')
295 self._start_servers()
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400296 self._create_load_balancer()
297 self._check_load_balancing()
298
299
300class NeutronRetriever(object):
Elena Ezhova4a27b462014-04-09 15:25:46 +0400301 """
302 Helper class to make possible handling neutron objects returned by GET
303 requests as attribute dicts.
304
305 Whet get() method is called, the returned dictionary is wrapped into
306 a corresponding DeletableResource class which provides attribute access
307 to dictionary values.
308
309 Usage:
310 This retriever is used to allow using status_timeout from
311 tempest.manager with Neutron objects.
312 """
313
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400314 def __init__(self, network_client, path, resource):
315 self.network_client = network_client
316 self.path = path
317 self.resource = resource
318
319 def get(self, thing_id):
320 obj = self.network_client.get(self.path % thing_id)
321 return self.resource(client=self.network_client, **obj.values()[0])