blob: 5f7146181341eba9447f2fface110bfd3f0e5900 [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
16import time
17import urllib
18
19from tempest.api.network import common as net_common
Elena Ezhovaa5105e62013-11-26 20:46:52 +040020from tempest import config
21from tempest import exceptions
22from tempest.scenario import manager
23from tempest import test
24
25config = config.CONF
26
27
28class TestLoadBalancerBasic(manager.NetworkScenarioTest):
29
30 """
31 This test checks basic load balancing.
32
33 The following is the scenario outline:
34 1. Create an instance
35 2. SSH to the instance and start two servers
36 3. Create a load balancer with two members and with ROUND_ROBIN algorithm
37 associate the VIP with a floating ip
38 4. Send 10 requests to the floating ip and check that they are shared
39 between the two servers and that both of them get equal portions
40 of the requests
41 """
42
43 @classmethod
44 def check_preconditions(cls):
45 super(TestLoadBalancerBasic, cls).check_preconditions()
46 cfg = config.network
47 if not test.is_extension_enabled('lbaas', 'network'):
48 msg = 'LBaaS Extension is not enabled'
49 cls.enabled = False
50 raise cls.skipException(msg)
51 if not (cfg.tenant_networks_reachable or cfg.public_network_id):
52 msg = ('Either tenant_networks_reachable must be "true", or '
53 'public_network_id must be defined.')
54 cls.enabled = False
55 raise cls.skipException(msg)
56
57 @classmethod
58 def setUpClass(cls):
59 super(TestLoadBalancerBasic, cls).setUpClass()
60 cls.check_preconditions()
Elena Ezhovaa5105e62013-11-26 20:46:52 +040061 cls.servers_keypairs = {}
Elena Ezhovaa5105e62013-11-26 20:46:52 +040062 cls.members = []
Elena Ezhovaa5105e62013-11-26 20:46:52 +040063 cls.floating_ips = {}
Elena Ezhova4a27b462014-04-09 15:25:46 +040064 cls.server_ips = {}
Elena Ezhovaa5105e62013-11-26 20:46:52 +040065 cls.port1 = 80
66 cls.port2 = 88
67
Elena Ezhova4a27b462014-04-09 15:25:46 +040068 def setUp(self):
69 super(TestLoadBalancerBasic, self).setUp()
70 self.server_ips = {}
71 self._create_security_group()
72
73 def cleanup_wrapper(self, resource):
74 self.cleanup_resource(resource, self.__class__.__name__)
75
76 def _create_security_group(self):
77 self.security_group = self._create_security_group_neutron(
78 tenant_id=self.tenant_id)
Eugene Nikanorov55d13142014-03-24 15:39:21 +040079 self._create_security_group_rules_for_port(self.port1)
80 self._create_security_group_rules_for_port(self.port2)
Elena Ezhova4a27b462014-04-09 15:25:46 +040081 self.addCleanup(self.cleanup_wrapper, self.security_group)
Eugene Nikanorov55d13142014-03-24 15:39:21 +040082
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 self.addCleanup(self.cleanup_wrapper, keypair)
99 security_groups = [self.security_group.name]
Elena Ezhova91531102014-02-07 17:25:58 +0400100 net = self._list_networks(tenant_id=self.tenant_id)[0]
Yair Fried2d2f3fe2014-02-24 16:19:20 +0200101 create_kwargs = {
102 'nics': [
Elena Ezhova91531102014-02-07 17:25:58 +0400103 {'net-id': net['id']},
Yair Fried2d2f3fe2014-02-24 16:19:20 +0200104 ],
105 'key_name': keypair.name,
106 'security_groups': security_groups,
107 }
108 server = self.create_server(name=name,
109 create_kwargs=create_kwargs)
Elena Ezhova4a27b462014-04-09 15:25:46 +0400110 self.addCleanup(self.cleanup_wrapper, server)
111 self.servers_keypairs[server.id] = keypair
Elena Ezhova91531102014-02-07 17:25:58 +0400112 if (config.network.public_network_id and not
113 config.network.tenant_networks_reachable):
114 public_network_id = config.network.public_network_id
115 floating_ip = self._create_floating_ip(
116 server, public_network_id)
Elena Ezhova4a27b462014-04-09 15:25:46 +0400117 self.addCleanup(self.cleanup_wrapper, floating_ip)
Elena Ezhova91531102014-02-07 17:25:58 +0400118 self.floating_ips[floating_ip] = server
Elena Ezhova4a27b462014-04-09 15:25:46 +0400119 self.server_ips[server.id] = floating_ip.floating_ip_address
Elena Ezhova91531102014-02-07 17:25:58 +0400120 else:
Elena Ezhova4a27b462014-04-09 15:25:46 +0400121 self.server_ips[server.id] = server.networks[net.name][0]
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400122 self.assertTrue(self.servers_keypairs)
Elena Ezhova91531102014-02-07 17:25:58 +0400123 return server
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400124
Elena Ezhova4a27b462014-04-09 15:25:46 +0400125 def _create_servers(self):
126 for count in range(2):
127 self._create_server(name=("server%s" % (count + 1)))
128 self.assertEqual(len(self.servers_keypairs), 2)
129
130 def _start_servers(self):
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400131 """
Elena Ezhova4a27b462014-04-09 15:25:46 +0400132 Start two backends
133
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400134 1. SSH to the instance
Elena Ezhova91531102014-02-07 17:25:58 +0400135 2. Start two http backends listening on ports 80 and 88 respectively
Elena Ezhova4a27b462014-04-09 15:25:46 +0400136 In case there are two instances, each backend is created on a separate
137 instance.
138
139 The backends are the inetd services. To start them we need to edit
140 /etc/inetd.conf in the following way:
141 www stream tcp nowait root /bin/sh sh /home/cirros/script_name
142
143 Where /home/cirros/script_name is a path to a script which
144 echoes the responses:
145 echo -e 'HTTP/1.0 200 OK\r\n\r\nserver_name
146
147 If we want the server to listen on port 88, then we use
148 "kerberos" instead of "www".
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400149 """
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400150
Elena Ezhova4a27b462014-04-09 15:25:46 +0400151 for server_id, ip in self.server_ips.iteritems():
152 private_key = self.servers_keypairs[server_id].private_key
153 server_name = self.compute_client.servers.get(server_id).name
154 ssh_client = self.get_remote_client(
155 server_or_ip=ip,
156 private_key=private_key)
157 ssh_client.validate_authentication()
158 # Create service for inetd
159 create_script = """sudo sh -c "echo -e \\"echo -e 'HTTP/1.0 """ \
160 """200 OK\\\\\\r\\\\\\n\\\\\\r\\\\\\n""" \
161 """%(server)s'\\" >>/home/cirros/%(script)s\""""
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400162
Elena Ezhova4a27b462014-04-09 15:25:46 +0400163 cmd = create_script % {
164 'server': server_name,
165 'script': 'script1'}
166 ssh_client.exec_command(cmd)
167 # Configure inetd
168 configure_inetd = """sudo sh -c "echo -e \\"%(service)s """ \
169 """stream tcp nowait root /bin/sh sh """ \
170 """/home/cirros/%(script)s\\" >> """ \
171 """/etc/inetd.conf\""""
172 # "www" stands for port 80
173 cmd = configure_inetd % {'service': 'www',
174 'script': 'script1'}
175 ssh_client.exec_command(cmd)
176
177 if len(self.server_ips) == 1:
178 cmd = create_script % {'server': 'server2',
179 'script': 'script2'}
180 ssh_client.exec_command(cmd)
181 # "kerberos" stands for port 88
182 cmd = configure_inetd % {'service': 'kerberos',
183 'script': 'script2'}
184 ssh_client.exec_command(cmd)
185
186 # Get PIDs of inetd
187 pids = ssh_client.get_pids('inetd')
188 if pids != ['']:
189 # If there are any inetd processes, reload them
190 kill_cmd = "sudo kill -HUP %s" % ' '.join(pids)
191 ssh_client.exec_command(kill_cmd)
192 else:
193 # In other case start inetd
194 start_inetd = "sudo /usr/sbin/inetd /etc/inetd.conf"
195 ssh_client.exec_command(start_inetd)
196
197 def _check_connection(self, check_ip, port=80):
198 def try_connect(ip, port):
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400199 try:
Elena Ezhova4a27b462014-04-09 15:25:46 +0400200 resp = urllib.urlopen("http://{0}:{1}/".format(ip, port))
201 if resp.getcode() == 200:
202 return True
203 return False
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400204 except IOError:
205 return False
206 timeout = config.compute.ping_timeout
Elena Ezhova4a27b462014-04-09 15:25:46 +0400207 start = time.time()
208 while not try_connect(check_ip, port):
209 if (time.time() - start) > timeout:
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400210 message = "Timed out trying to connect to %s" % check_ip
211 raise exceptions.TimeoutException(message)
212
213 def _create_pool(self):
214 """Create a pool with ROUND_ROBIN algorithm."""
Yair Fried2d2f3fe2014-02-24 16:19:20 +0200215 # get tenant subnet and verify there's only one
216 subnet = self._list_subnets(tenant_id=self.tenant_id)[0]
217 self.subnet = net_common.DeletableSubnet(client=self.network_client,
Elena Ezhova91531102014-02-07 17:25:58 +0400218 **subnet)
Yair Fried2d2f3fe2014-02-24 16:19:20 +0200219 self.pool = super(TestLoadBalancerBasic, self)._create_pool(
Elena Ezhova4a27b462014-04-09 15:25:46 +0400220 lb_method='ROUND_ROBIN',
221 protocol='HTTP',
222 subnet_id=self.subnet.id)
223 self.addCleanup(self.cleanup_wrapper, self.pool)
Yair Fried2d2f3fe2014-02-24 16:19:20 +0200224 self.assertTrue(self.pool)
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400225
Elena Ezhova4a27b462014-04-09 15:25:46 +0400226 def _create_members(self):
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400227 """
228 Create two members.
229
230 In case there is only one server, create both members with the same ip
231 but with different ports to listen on.
232 """
Elena Ezhova4a27b462014-04-09 15:25:46 +0400233
234 for server_id, ip in self.server_ips.iteritems():
235 if len(self.server_ips) == 1:
236 member1 = self._create_member(address=ip,
237 protocol_port=self.port1,
238 pool_id=self.pool.id)
239 self.addCleanup(self.cleanup_wrapper, member1)
240 member2 = self._create_member(address=ip,
241 protocol_port=self.port2,
242 pool_id=self.pool.id)
243 self.addCleanup(self.cleanup_wrapper, member2)
244 self.members.extend([member1, member2])
245 else:
246 member = self._create_member(address=ip,
247 protocol_port=self.port1,
248 pool_id=self.pool.id)
249 self.addCleanup(self.cleanup_wrapper, member)
250 self.members.append(member)
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400251 self.assertTrue(self.members)
252
253 def _assign_floating_ip_to_vip(self, vip):
254 public_network_id = config.network.public_network_id
Yair Fried2d2f3fe2014-02-24 16:19:20 +0200255 port_id = vip.port_id
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200256 floating_ip = self._create_floating_ip(vip, public_network_id,
257 port_id=port_id)
Elena Ezhova4a27b462014-04-09 15:25:46 +0400258 self.addCleanup(self.cleanup_wrapper, floating_ip)
Yair Fried2d2f3fe2014-02-24 16:19:20 +0200259 self.floating_ips.setdefault(vip.id, [])
260 self.floating_ips[vip.id].append(floating_ip)
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400261
262 def _create_load_balancer(self):
263 self._create_pool()
Elena Ezhova4a27b462014-04-09 15:25:46 +0400264 self._create_members()
265 self.vip = self._create_vip(protocol='HTTP',
266 protocol_port=80,
267 subnet_id=self.subnet.id,
268 pool_id=self.pool.id)
269 self.addCleanup(self.cleanup_wrapper, self.vip)
270 self.status_timeout(NeutronRetriever(self.network_client,
271 self.network_client.vip_path,
272 net_common.DeletableVip),
273 self.vip.id,
274 expected_status='ACTIVE')
Elena Ezhova91531102014-02-07 17:25:58 +0400275 if (config.network.public_network_id and not
276 config.network.tenant_networks_reachable):
277 self._assign_floating_ip_to_vip(self.vip)
278 self.vip_ip = self.floating_ips[
279 self.vip.id][0]['floating_ip_address']
280 else:
281 self.vip_ip = self.vip.address
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400282
283 def _check_load_balancing(self):
284 """
Elena Ezhova4a27b462014-04-09 15:25:46 +0400285 1. Send 100 requests on the floating ip associated with the VIP
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400286 2. Check that the requests are shared between
287 the two servers and that both of them get equal portions
288 of the requests
289 """
290
Elena Ezhova91531102014-02-07 17:25:58 +0400291 self._check_connection(self.vip_ip)
Elena Ezhova4a27b462014-04-09 15:25:46 +0400292 resp = self._send_requests(self.vip_ip)
293 self.assertEqual(set(["server1\n", "server2\n"]), set(resp))
294 self.assertEqual(50, resp.count("server1\n"))
295 self.assertEqual(50, resp.count("server2\n"))
296
297 def _send_requests(self, vip_ip):
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400298 resp = []
Elena Ezhova4a27b462014-04-09 15:25:46 +0400299 for count in range(100):
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400300 resp.append(
301 urllib.urlopen(
Elena Ezhova4a27b462014-04-09 15:25:46 +0400302 "http://{0}/".format(vip_ip)).read())
303 return resp
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400304
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400305 @test.attr(type='smoke')
306 @test.services('compute', 'network')
307 def test_load_balancer_basic(self):
Elena Ezhova4a27b462014-04-09 15:25:46 +0400308 self._create_server('server1')
309 self._start_servers()
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400310 self._create_load_balancer()
311 self._check_load_balancing()
312
313
314class NeutronRetriever(object):
Elena Ezhova4a27b462014-04-09 15:25:46 +0400315 """
316 Helper class to make possible handling neutron objects returned by GET
317 requests as attribute dicts.
318
319 Whet get() method is called, the returned dictionary is wrapped into
320 a corresponding DeletableResource class which provides attribute access
321 to dictionary values.
322
323 Usage:
324 This retriever is used to allow using status_timeout from
325 tempest.manager with Neutron objects.
326 """
327
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400328 def __init__(self, network_client, path, resource):
329 self.network_client = network_client
330 self.path = path
331 self.resource = resource
332
333 def get(self, thing_id):
334 obj = self.network_client.get(self.path % thing_id)
335 return self.resource(client=self.network_client, **obj.values()[0])