blob: 826da48de7ebe927be834f5456ec3b1d7070aa19 [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
77 def cleanup_wrapper(self, resource):
78 self.cleanup_resource(resource, self.__class__.__name__)
79
80 def _create_security_group(self):
81 self.security_group = self._create_security_group_neutron(
82 tenant_id=self.tenant_id)
Eugene Nikanorov55d13142014-03-24 15:39:21 +040083 self._create_security_group_rules_for_port(self.port1)
84 self._create_security_group_rules_for_port(self.port2)
Elena Ezhova4a27b462014-04-09 15:25:46 +040085 self.addCleanup(self.cleanup_wrapper, self.security_group)
Eugene Nikanorov55d13142014-03-24 15:39:21 +040086
87 def _create_security_group_rules_for_port(self, port):
88 rule = {
89 'direction': 'ingress',
90 'protocol': 'tcp',
91 'port_range_min': port,
92 'port_range_max': port,
93 }
94 self._create_security_group_rule(
95 client=self.network_client,
Elena Ezhova4a27b462014-04-09 15:25:46 +040096 secgroup=self.security_group,
Eugene Nikanorov55d13142014-03-24 15:39:21 +040097 tenant_id=self.tenant_id,
98 **rule)
Elena Ezhovaa5105e62013-11-26 20:46:52 +040099
Elena Ezhova4a27b462014-04-09 15:25:46 +0400100 def _create_server(self, name):
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400101 keypair = self.create_keypair(name='keypair-%s' % name)
Elena Ezhova4a27b462014-04-09 15:25:46 +0400102 self.addCleanup(self.cleanup_wrapper, keypair)
103 security_groups = [self.security_group.name]
Elena Ezhova91531102014-02-07 17:25:58 +0400104 net = self._list_networks(tenant_id=self.tenant_id)[0]
Yair Fried2d2f3fe2014-02-24 16:19:20 +0200105 create_kwargs = {
106 'nics': [
Elena Ezhova91531102014-02-07 17:25:58 +0400107 {'net-id': net['id']},
Yair Fried2d2f3fe2014-02-24 16:19:20 +0200108 ],
109 'key_name': keypair.name,
110 'security_groups': security_groups,
111 }
112 server = self.create_server(name=name,
113 create_kwargs=create_kwargs)
Elena Ezhova4a27b462014-04-09 15:25:46 +0400114 self.addCleanup(self.cleanup_wrapper, server)
115 self.servers_keypairs[server.id] = keypair
Elena Ezhova91531102014-02-07 17:25:58 +0400116 if (config.network.public_network_id and not
117 config.network.tenant_networks_reachable):
118 public_network_id = config.network.public_network_id
119 floating_ip = self._create_floating_ip(
120 server, public_network_id)
Elena Ezhova4a27b462014-04-09 15:25:46 +0400121 self.addCleanup(self.cleanup_wrapper, floating_ip)
Elena Ezhova91531102014-02-07 17:25:58 +0400122 self.floating_ips[floating_ip] = server
Elena Ezhova4a27b462014-04-09 15:25:46 +0400123 self.server_ips[server.id] = floating_ip.floating_ip_address
Elena Ezhova91531102014-02-07 17:25:58 +0400124 else:
Zhi Kun Liucd2e3772014-04-15 21:18:18 -0500125 self.server_ips[server.id] = server.networks[net['name']][0]
Darragh O'Reilly7c8176e2014-04-26 16:03:23 +0000126 self.server_fixed_ips[server.id] = server.networks[net['name']][0]
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400127 self.assertTrue(self.servers_keypairs)
Elena Ezhova91531102014-02-07 17:25:58 +0400128 return server
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400129
Elena Ezhova4a27b462014-04-09 15:25:46 +0400130 def _create_servers(self):
131 for count in range(2):
132 self._create_server(name=("server%s" % (count + 1)))
133 self.assertEqual(len(self.servers_keypairs), 2)
134
135 def _start_servers(self):
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400136 """
Elena Ezhova4a27b462014-04-09 15:25:46 +0400137 Start two backends
138
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400139 1. SSH to the instance
Elena Ezhova91531102014-02-07 17:25:58 +0400140 2. Start two http backends listening on ports 80 and 88 respectively
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400141 """
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400142
Elena Ezhova4a27b462014-04-09 15:25:46 +0400143 for server_id, ip in self.server_ips.iteritems():
144 private_key = self.servers_keypairs[server_id].private_key
145 server_name = self.compute_client.servers.get(server_id).name
Elena Ezhova6e73f462014-04-18 17:38:13 +0400146 username = config.scenario.ssh_user
Elena Ezhova4a27b462014-04-09 15:25:46 +0400147 ssh_client = self.get_remote_client(
148 server_or_ip=ip,
149 private_key=private_key)
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400150
Elena Ezhova6e73f462014-04-18 17:38:13 +0400151 # Write a backend's responce into a file
Elena Ezhova0c8e3292014-06-05 12:15:39 +0400152 resp = """echo -ne "HTTP/1.1 200 OK\r\nContent-Length: 7\r\n""" \
153 """Connection: close\r\nContent-Type: text/html; """ \
154 """charset=UTF-8\r\n\r\n%s"; cat >/dev/null"""
155
Elena Ezhova6e73f462014-04-18 17:38:13 +0400156 with tempfile.NamedTemporaryFile() as script:
157 script.write(resp % server_name)
158 script.flush()
159 with tempfile.NamedTemporaryFile() as key:
160 key.write(private_key)
161 key.flush()
162 commands.copy_file_to_host(script.name,
Elena Ezhova0c8e3292014-06-05 12:15:39 +0400163 "/tmp/script1",
Elena Ezhova6e73f462014-04-18 17:38:13 +0400164 ip,
165 username, key.name)
Elena Ezhova0c8e3292014-06-05 12:15:39 +0400166
Elena Ezhova6e73f462014-04-18 17:38:13 +0400167 # Start netcat
Elena Ezhova0c8e3292014-06-05 12:15:39 +0400168 start_server = """sudo nc -ll -p %(port)s -e sh """ \
169 """/tmp/%(script)s &"""
Elena Ezhova6e73f462014-04-18 17:38:13 +0400170 cmd = start_server % {'port': self.port1,
171 'script': 'script1'}
Elena Ezhova4a27b462014-04-09 15:25:46 +0400172 ssh_client.exec_command(cmd)
Elena Ezhova0c8e3292014-06-05 12:15:39 +0400173
Elena Ezhova4a27b462014-04-09 15:25:46 +0400174 if len(self.server_ips) == 1:
Elena Ezhova6e73f462014-04-18 17:38:13 +0400175 with tempfile.NamedTemporaryFile() as script:
176 script.write(resp % 'server2')
177 script.flush()
178 with tempfile.NamedTemporaryFile() as key:
179 key.write(private_key)
180 key.flush()
181 commands.copy_file_to_host(script.name,
Elena Ezhova0c8e3292014-06-05 12:15:39 +0400182 "/tmp/script2", ip,
Elena Ezhova6e73f462014-04-18 17:38:13 +0400183 username, key.name)
184 cmd = start_server % {'port': self.port2,
185 'script': 'script2'}
Elena Ezhova4a27b462014-04-09 15:25:46 +0400186 ssh_client.exec_command(cmd)
Elena Ezhova4a27b462014-04-09 15:25:46 +0400187
188 def _check_connection(self, check_ip, port=80):
189 def try_connect(ip, port):
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400190 try:
Elena Ezhova6e73f462014-04-18 17:38:13 +0400191 resp = urllib2.urlopen("http://{0}:{1}/".format(ip, port))
Elena Ezhova4a27b462014-04-09 15:25:46 +0400192 if resp.getcode() == 200:
193 return True
194 return False
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400195 except IOError:
196 return False
197 timeout = config.compute.ping_timeout
Elena Ezhova4a27b462014-04-09 15:25:46 +0400198 start = time.time()
199 while not try_connect(check_ip, port):
200 if (time.time() - start) > timeout:
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400201 message = "Timed out trying to connect to %s" % check_ip
202 raise exceptions.TimeoutException(message)
203
204 def _create_pool(self):
205 """Create a pool with ROUND_ROBIN algorithm."""
Yair Fried2d2f3fe2014-02-24 16:19:20 +0200206 # get tenant subnet and verify there's only one
207 subnet = self._list_subnets(tenant_id=self.tenant_id)[0]
208 self.subnet = net_common.DeletableSubnet(client=self.network_client,
Elena Ezhova91531102014-02-07 17:25:58 +0400209 **subnet)
Yair Fried2d2f3fe2014-02-24 16:19:20 +0200210 self.pool = super(TestLoadBalancerBasic, self)._create_pool(
Elena Ezhova4a27b462014-04-09 15:25:46 +0400211 lb_method='ROUND_ROBIN',
212 protocol='HTTP',
213 subnet_id=self.subnet.id)
214 self.addCleanup(self.cleanup_wrapper, self.pool)
Yair Fried2d2f3fe2014-02-24 16:19:20 +0200215 self.assertTrue(self.pool)
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400216
Elena Ezhova4a27b462014-04-09 15:25:46 +0400217 def _create_members(self):
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400218 """
219 Create two members.
220
221 In case there is only one server, create both members with the same ip
222 but with different ports to listen on.
223 """
Elena Ezhova4a27b462014-04-09 15:25:46 +0400224
Darragh O'Reilly7c8176e2014-04-26 16:03:23 +0000225 for server_id, ip in self.server_fixed_ips.iteritems():
226 if len(self.server_fixed_ips) == 1:
Elena Ezhova4a27b462014-04-09 15:25:46 +0400227 member1 = self._create_member(address=ip,
228 protocol_port=self.port1,
229 pool_id=self.pool.id)
230 self.addCleanup(self.cleanup_wrapper, member1)
231 member2 = self._create_member(address=ip,
232 protocol_port=self.port2,
233 pool_id=self.pool.id)
234 self.addCleanup(self.cleanup_wrapper, member2)
235 self.members.extend([member1, member2])
236 else:
237 member = self._create_member(address=ip,
238 protocol_port=self.port1,
239 pool_id=self.pool.id)
240 self.addCleanup(self.cleanup_wrapper, member)
241 self.members.append(member)
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400242 self.assertTrue(self.members)
243
244 def _assign_floating_ip_to_vip(self, vip):
245 public_network_id = config.network.public_network_id
Yair Fried2d2f3fe2014-02-24 16:19:20 +0200246 port_id = vip.port_id
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200247 floating_ip = self._create_floating_ip(vip, public_network_id,
248 port_id=port_id)
Elena Ezhova4a27b462014-04-09 15:25:46 +0400249 self.addCleanup(self.cleanup_wrapper, floating_ip)
Yair Fried2d2f3fe2014-02-24 16:19:20 +0200250 self.floating_ips.setdefault(vip.id, [])
251 self.floating_ips[vip.id].append(floating_ip)
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400252
253 def _create_load_balancer(self):
254 self._create_pool()
Elena Ezhova4a27b462014-04-09 15:25:46 +0400255 self._create_members()
256 self.vip = self._create_vip(protocol='HTTP',
257 protocol_port=80,
258 subnet_id=self.subnet.id,
259 pool_id=self.pool.id)
260 self.addCleanup(self.cleanup_wrapper, self.vip)
261 self.status_timeout(NeutronRetriever(self.network_client,
262 self.network_client.vip_path,
263 net_common.DeletableVip),
264 self.vip.id,
265 expected_status='ACTIVE')
Elena Ezhova91531102014-02-07 17:25:58 +0400266 if (config.network.public_network_id and not
267 config.network.tenant_networks_reachable):
268 self._assign_floating_ip_to_vip(self.vip)
269 self.vip_ip = self.floating_ips[
270 self.vip.id][0]['floating_ip_address']
271 else:
272 self.vip_ip = self.vip.address
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400273
274 def _check_load_balancing(self):
275 """
Elena Ezhova6e73f462014-04-18 17:38:13 +0400276 1. Send 10 requests on the floating ip associated with the VIP
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400277 2. Check that the requests are shared between
278 the two servers and that both of them get equal portions
279 of the requests
280 """
281
Elena Ezhova91531102014-02-07 17:25:58 +0400282 self._check_connection(self.vip_ip)
Elena Ezhova6e73f462014-04-18 17:38:13 +0400283 self._send_requests(self.vip_ip, set(["server1", "server2"]))
Elena Ezhova4a27b462014-04-09 15:25:46 +0400284
Elena Ezhova6e73f462014-04-18 17:38:13 +0400285 def _send_requests(self, vip_ip, expected, num_req=10):
286 count = 0
287 while count < num_req:
Elena Ezhova0c8e3292014-06-05 12:15:39 +0400288 resp = []
289 for i in range(len(self.members)):
290 resp.append(
291 urllib2.urlopen(
292 "http://{0}/".format(vip_ip)).read())
293 count += 1
294 self.assertEqual(expected,
295 set(resp))
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400296
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400297 @test.attr(type='smoke')
298 @test.services('compute', 'network')
299 def test_load_balancer_basic(self):
Elena Ezhova4a27b462014-04-09 15:25:46 +0400300 self._create_server('server1')
301 self._start_servers()
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400302 self._create_load_balancer()
303 self._check_load_balancing()
304
305
306class NeutronRetriever(object):
Elena Ezhova4a27b462014-04-09 15:25:46 +0400307 """
308 Helper class to make possible handling neutron objects returned by GET
309 requests as attribute dicts.
310
311 Whet get() method is called, the returned dictionary is wrapped into
312 a corresponding DeletableResource class which provides attribute access
313 to dictionary values.
314
315 Usage:
316 This retriever is used to allow using status_timeout from
317 tempest.manager with Neutron objects.
318 """
319
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400320 def __init__(self, network_client, path, resource):
321 self.network_client = network_client
322 self.path = path
323 self.resource = resource
324
325 def get(self, thing_id):
326 obj = self.network_client.get(self.path % thing_id)
327 return self.resource(client=self.network_client, **obj.values()[0])