blob: 35e50e8423f1447e51e4cc2a8010f8e22fb57a1f [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()
Adam Gandelmanbbf5c472014-08-12 16:46:12 -070076 self._set_net_and_subnet()
77
78 def _set_net_and_subnet(self):
79 """
80 Query and set appropriate network and subnet attributes to be used
81 for the test. Existing tenant networks are used if they are found.
82 The configured private network and associated subnet is used as a
83 fallback in absence of tenant networking.
84 """
85 try:
86 tenant_net = self._list_networks(tenant_id=self.tenant_id)[0]
87 except IndexError:
88 tenant_net = None
89
90 if tenant_net:
91 tenant_subnet = self._list_subnets(tenant_id=self.tenant_id)[0]
92 self.subnet = net_common.DeletableSubnet(
93 client=self.network_client,
94 **tenant_subnet)
95 self.network = tenant_net
96 else:
97 self.network = self._get_network_by_name(
98 config.compute.fixed_network_name)
99 # TODO(adam_g): We are assuming that the first subnet associated
100 # with the fixed network is the one we want. In the future, we
101 # should instead pull a subnet id from config, which is set by
102 # devstack/admin/etc.
103 subnet = self._list_subnets(network_id=self.network['id'])[0]
104 self.subnet = net_common.AttributeDict(subnet)
Elena Ezhova4a27b462014-04-09 15:25:46 +0400105
Elena Ezhova4a27b462014-04-09 15:25:46 +0400106 def _create_security_group(self):
107 self.security_group = self._create_security_group_neutron(
108 tenant_id=self.tenant_id)
Eugene Nikanorov55d13142014-03-24 15:39:21 +0400109 self._create_security_group_rules_for_port(self.port1)
110 self._create_security_group_rules_for_port(self.port2)
111
112 def _create_security_group_rules_for_port(self, port):
113 rule = {
114 'direction': 'ingress',
115 'protocol': 'tcp',
116 'port_range_min': port,
117 'port_range_max': port,
118 }
119 self._create_security_group_rule(
120 client=self.network_client,
Elena Ezhova4a27b462014-04-09 15:25:46 +0400121 secgroup=self.security_group,
Eugene Nikanorov55d13142014-03-24 15:39:21 +0400122 tenant_id=self.tenant_id,
123 **rule)
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400124
Elena Ezhova4a27b462014-04-09 15:25:46 +0400125 def _create_server(self, name):
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400126 keypair = self.create_keypair(name='keypair-%s' % name)
Elena Ezhova4a27b462014-04-09 15:25:46 +0400127 security_groups = [self.security_group.name]
Yair Fried2d2f3fe2014-02-24 16:19:20 +0200128 create_kwargs = {
129 'nics': [
Adam Gandelmanbbf5c472014-08-12 16:46:12 -0700130 {'net-id': self.network['id']},
Yair Fried2d2f3fe2014-02-24 16:19:20 +0200131 ],
132 'key_name': keypair.name,
133 'security_groups': security_groups,
134 }
135 server = self.create_server(name=name,
136 create_kwargs=create_kwargs)
Elena Ezhova4a27b462014-04-09 15:25:46 +0400137 self.servers_keypairs[server.id] = keypair
Adam Gandelmanbbf5c472014-08-12 16:46:12 -0700138 net_name = self.network['name']
Elena Ezhova91531102014-02-07 17:25:58 +0400139 if (config.network.public_network_id and not
140 config.network.tenant_networks_reachable):
141 public_network_id = config.network.public_network_id
142 floating_ip = self._create_floating_ip(
143 server, public_network_id)
144 self.floating_ips[floating_ip] = server
Elena Ezhova4a27b462014-04-09 15:25:46 +0400145 self.server_ips[server.id] = floating_ip.floating_ip_address
Elena Ezhova91531102014-02-07 17:25:58 +0400146 else:
Adam Gandelmanbbf5c472014-08-12 16:46:12 -0700147 self.server_ips[server.id] = server.networks[net_name][0]
148 self.server_fixed_ips[server.id] = server.networks[net_name][0]
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400149 self.assertTrue(self.servers_keypairs)
Elena Ezhova91531102014-02-07 17:25:58 +0400150 return server
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400151
Elena Ezhova4a27b462014-04-09 15:25:46 +0400152 def _create_servers(self):
153 for count in range(2):
154 self._create_server(name=("server%s" % (count + 1)))
155 self.assertEqual(len(self.servers_keypairs), 2)
156
157 def _start_servers(self):
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400158 """
Elena Ezhova4a27b462014-04-09 15:25:46 +0400159 Start two backends
160
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400161 1. SSH to the instance
Elena Ezhova91531102014-02-07 17:25:58 +0400162 2. Start two http backends listening on ports 80 and 88 respectively
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400163 """
Elena Ezhova4a27b462014-04-09 15:25:46 +0400164 for server_id, ip in self.server_ips.iteritems():
165 private_key = self.servers_keypairs[server_id].private_key
166 server_name = self.compute_client.servers.get(server_id).name
Elena Ezhova6e73f462014-04-18 17:38:13 +0400167 username = config.scenario.ssh_user
Elena Ezhova4a27b462014-04-09 15:25:46 +0400168 ssh_client = self.get_remote_client(
169 server_or_ip=ip,
170 private_key=private_key)
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400171
Robert Mizielskie1d88992014-07-15 15:28:09 +0200172 # Write a backend's response into a file
Elena Ezhova0c8e3292014-06-05 12:15:39 +0400173 resp = """echo -ne "HTTP/1.1 200 OK\r\nContent-Length: 7\r\n""" \
174 """Connection: close\r\nContent-Type: text/html; """ \
175 """charset=UTF-8\r\n\r\n%s"; cat >/dev/null"""
176
Elena Ezhova6e73f462014-04-18 17:38:13 +0400177 with tempfile.NamedTemporaryFile() as script:
178 script.write(resp % server_name)
179 script.flush()
180 with tempfile.NamedTemporaryFile() as key:
181 key.write(private_key)
182 key.flush()
183 commands.copy_file_to_host(script.name,
Elena Ezhova0c8e3292014-06-05 12:15:39 +0400184 "/tmp/script1",
Elena Ezhova6e73f462014-04-18 17:38:13 +0400185 ip,
186 username, key.name)
Elena Ezhova0c8e3292014-06-05 12:15:39 +0400187
Elena Ezhova6e73f462014-04-18 17:38:13 +0400188 # Start netcat
Elena Ezhova0c8e3292014-06-05 12:15:39 +0400189 start_server = """sudo nc -ll -p %(port)s -e sh """ \
190 """/tmp/%(script)s &"""
Elena Ezhova6e73f462014-04-18 17:38:13 +0400191 cmd = start_server % {'port': self.port1,
192 'script': 'script1'}
Elena Ezhova4a27b462014-04-09 15:25:46 +0400193 ssh_client.exec_command(cmd)
Elena Ezhova0c8e3292014-06-05 12:15:39 +0400194
Elena Ezhova4a27b462014-04-09 15:25:46 +0400195 if len(self.server_ips) == 1:
Elena Ezhova6e73f462014-04-18 17:38:13 +0400196 with tempfile.NamedTemporaryFile() as script:
197 script.write(resp % 'server2')
198 script.flush()
199 with tempfile.NamedTemporaryFile() as key:
200 key.write(private_key)
201 key.flush()
202 commands.copy_file_to_host(script.name,
Elena Ezhova0c8e3292014-06-05 12:15:39 +0400203 "/tmp/script2", ip,
Elena Ezhova6e73f462014-04-18 17:38:13 +0400204 username, key.name)
205 cmd = start_server % {'port': self.port2,
206 'script': 'script2'}
Elena Ezhova4a27b462014-04-09 15:25:46 +0400207 ssh_client.exec_command(cmd)
Elena Ezhova4a27b462014-04-09 15:25:46 +0400208
209 def _check_connection(self, check_ip, port=80):
210 def try_connect(ip, port):
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400211 try:
Elena Ezhova6e73f462014-04-18 17:38:13 +0400212 resp = urllib2.urlopen("http://{0}:{1}/".format(ip, port))
Elena Ezhova4a27b462014-04-09 15:25:46 +0400213 if resp.getcode() == 200:
214 return True
215 return False
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400216 except IOError:
217 return False
218 timeout = config.compute.ping_timeout
Elena Ezhova4a27b462014-04-09 15:25:46 +0400219 start = time.time()
220 while not try_connect(check_ip, port):
221 if (time.time() - start) > timeout:
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400222 message = "Timed out trying to connect to %s" % check_ip
223 raise exceptions.TimeoutException(message)
224
225 def _create_pool(self):
226 """Create a pool with ROUND_ROBIN algorithm."""
Yair Fried2d2f3fe2014-02-24 16:19:20 +0200227 self.pool = super(TestLoadBalancerBasic, self)._create_pool(
Elena Ezhova4a27b462014-04-09 15:25:46 +0400228 lb_method='ROUND_ROBIN',
229 protocol='HTTP',
230 subnet_id=self.subnet.id)
Yair Fried2d2f3fe2014-02-24 16:19:20 +0200231 self.assertTrue(self.pool)
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400232
Elena Ezhova4a27b462014-04-09 15:25:46 +0400233 def _create_members(self):
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400234 """
235 Create two members.
236
237 In case there is only one server, create both members with the same ip
238 but with different ports to listen on.
239 """
Elena Ezhova4a27b462014-04-09 15:25:46 +0400240
Darragh O'Reilly7c8176e2014-04-26 16:03:23 +0000241 for server_id, ip in self.server_fixed_ips.iteritems():
242 if len(self.server_fixed_ips) == 1:
Elena Ezhova4a27b462014-04-09 15:25:46 +0400243 member1 = self._create_member(address=ip,
244 protocol_port=self.port1,
245 pool_id=self.pool.id)
Elena Ezhova4a27b462014-04-09 15:25:46 +0400246 member2 = self._create_member(address=ip,
247 protocol_port=self.port2,
248 pool_id=self.pool.id)
Elena Ezhova4a27b462014-04-09 15:25:46 +0400249 self.members.extend([member1, member2])
250 else:
251 member = self._create_member(address=ip,
252 protocol_port=self.port1,
253 pool_id=self.pool.id)
Elena Ezhova4a27b462014-04-09 15:25:46 +0400254 self.members.append(member)
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400255 self.assertTrue(self.members)
256
257 def _assign_floating_ip_to_vip(self, vip):
258 public_network_id = config.network.public_network_id
Yair Fried2d2f3fe2014-02-24 16:19:20 +0200259 port_id = vip.port_id
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200260 floating_ip = self._create_floating_ip(vip, public_network_id,
261 port_id=port_id)
Yair Fried2d2f3fe2014-02-24 16:19:20 +0200262 self.floating_ips.setdefault(vip.id, [])
263 self.floating_ips[vip.id].append(floating_ip)
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400264
265 def _create_load_balancer(self):
266 self._create_pool()
Elena Ezhova4a27b462014-04-09 15:25:46 +0400267 self._create_members()
268 self.vip = self._create_vip(protocol='HTTP',
269 protocol_port=80,
270 subnet_id=self.subnet.id,
271 pool_id=self.pool.id)
Elena Ezhova4a27b462014-04-09 15:25:46 +0400272 self.status_timeout(NeutronRetriever(self.network_client,
273 self.network_client.vip_path,
274 net_common.DeletableVip),
275 self.vip.id,
276 expected_status='ACTIVE')
Elena Ezhova91531102014-02-07 17:25:58 +0400277 if (config.network.public_network_id and not
278 config.network.tenant_networks_reachable):
279 self._assign_floating_ip_to_vip(self.vip)
280 self.vip_ip = self.floating_ips[
281 self.vip.id][0]['floating_ip_address']
282 else:
283 self.vip_ip = self.vip.address
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400284
Darragh O'Reilly44fc88a2014-04-26 15:42:47 +0000285 # Currently the ovs-agent is not enforcing security groups on the
286 # vip port - see https://bugs.launchpad.net/neutron/+bug/1163569
287 # However the linuxbridge-agent does, and it is necessary to add a
288 # security group with a rule that allows tcp port 80 to the vip port.
289 body = {'port': {'security_groups': [self.security_group.id]}}
290 self.network_client.update_port(self.vip.port_id, body)
291
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400292 def _check_load_balancing(self):
293 """
Elena Ezhova6e73f462014-04-18 17:38:13 +0400294 1. Send 10 requests on the floating ip associated with the VIP
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400295 2. Check that the requests are shared between
296 the two servers and that both of them get equal portions
297 of the requests
298 """
299
Elena Ezhova91531102014-02-07 17:25:58 +0400300 self._check_connection(self.vip_ip)
Elena Ezhova6e73f462014-04-18 17:38:13 +0400301 self._send_requests(self.vip_ip, set(["server1", "server2"]))
Elena Ezhova4a27b462014-04-09 15:25:46 +0400302
Elena Ezhova6e73f462014-04-18 17:38:13 +0400303 def _send_requests(self, vip_ip, expected, num_req=10):
304 count = 0
305 while count < num_req:
Elena Ezhova0c8e3292014-06-05 12:15:39 +0400306 resp = []
307 for i in range(len(self.members)):
308 resp.append(
309 urllib2.urlopen(
310 "http://{0}/".format(vip_ip)).read())
311 count += 1
312 self.assertEqual(expected,
313 set(resp))
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400314
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400315 @test.services('compute', 'network')
316 def test_load_balancer_basic(self):
Elena Ezhova4a27b462014-04-09 15:25:46 +0400317 self._create_server('server1')
318 self._start_servers()
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400319 self._create_load_balancer()
320 self._check_load_balancing()
321
322
323class NeutronRetriever(object):
Elena Ezhova4a27b462014-04-09 15:25:46 +0400324 """
325 Helper class to make possible handling neutron objects returned by GET
326 requests as attribute dicts.
327
328 Whet get() method is called, the returned dictionary is wrapped into
329 a corresponding DeletableResource class which provides attribute access
330 to dictionary values.
331
332 Usage:
333 This retriever is used to allow using status_timeout from
334 tempest.manager with Neutron objects.
335 """
336
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400337 def __init__(self, network_client, path, resource):
338 self.network_client = network_client
339 self.path = path
340 self.resource = resource
341
342 def get(self, thing_id):
343 obj = self.network_client.get(self.path % thing_id)
344 return self.resource(client=self.network_client, **obj.values()[0])