blob: fccca0ccb90ae7506f575b1cad21a839a73fb0f0 [file] [log] [blame]
Jude Cross638c4ef2017-07-24 14:57:20 -07001# Copyright 2017 GoDaddy
2# Copyright 2017 Catalyst IT Ltd
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.
15import time
16
17from oslo_log import log as logging
18import requests
19from tempest import config
20from tempest.lib.common.utils import data_utils
21from tempest.lib.common.utils import test_utils
22from tempest.lib import exceptions as lib_exc
23from tempest import test
24import tenacity
25
26from octavia_tempest_plugin.tests import server_util
27
28CONF = config.CONF
29LOG = logging.getLogger(__name__)
30
31
32class BaseLoadbalancerTest(test.BaseTestCase):
33 credentials = (['lbmember', CONF.loadbalancer.member_role], 'admin')
34 name_prefix = 'Tempest-BaseLoadbalancerTest'
35 vip_network_id = None
36 vip_subnet_id = None
37 vip_address = None
38 member_subnet_id = None
39 member_network_id = None
40 vm_ip = None
41
42 @classmethod
43 def skip_checks(cls):
44 super(BaseLoadbalancerTest, cls).skip_checks()
45
46 if not CONF.service_available.loadbalancer:
47 raise cls.skipException("Loadbalancing service is not available.")
48
49 service_list = {
50 'loadbalancing': CONF.service_available.loadbalancer,
51 'compute': CONF.service_available.nova,
52 'image': CONF.service_available.glance,
53 'neutron': CONF.service_available.neutron
54 }
55 for srv, available in service_list.items():
56 if not available:
57 raise cls.skipException("Service %s is not available." % srv)
58
59 @classmethod
60 def setup_clients(cls):
61 super(BaseLoadbalancerTest, cls).setup_clients()
62
63 cls.lb_client = cls.os_roles_lbmember.octavia_v2.LoadbalancerClient()
64 cls.servers_client = cls.os_roles_lbmember.servers_client
65 cls.networks_client = cls.os_roles_lbmember.networks_client
66 cls.subnets_client = cls.os_roles_lbmember.subnets_client
67 cls.interfaces_client = cls.os_roles_lbmember.interfaces_client
68 cls.sg_rule_client = cls.os_roles_lbmember.security_group_rules_client
69 cls.floatingip_client = cls.os_roles_lbmember.floating_ips_client
Jude Cross638c4ef2017-07-24 14:57:20 -070070 cls.routers_adm_client = cls.os_admin.routers_client
71
72 if CONF.identity.auth_version == 'v3':
73 project_id = cls.os_roles_lbmember.auth_provider.auth_data[1][
74 'project']['id']
75 else:
76 project_id = cls.os_roles_lbmember.auth_provider.auth_data[
77 1]['token']['tenant']['id']
78
79 cls.tenant_id = project_id
80 cls.user_id = cls.os_roles_lbmember.auth_provider.auth_data[1][
81 'user']['id']
82
83 @classmethod
84 def resource_setup(cls):
85 """Creates network resources."""
86 super(BaseLoadbalancerTest, cls).resource_setup()
87 if not CONF.loadbalancer.vip_network_id:
88 network_name = data_utils.rand_name(
89 'network',
90 prefix=cls.name_prefix
91 )
92 body = cls.networks_client.create_network(name=network_name)
93 cls.vip_network_id = body['network']['id']
94 cls.addClassResourceCleanup(
95 test_utils.call_and_ignore_notfound_exc,
96 cls.networks_client.delete_network,
97 cls.vip_network_id
98 )
99
100 subnet_name = data_utils.rand_name(
101 'subnet',
102 prefix=cls.name_prefix
103 )
104 body = cls.subnets_client.create_subnet(
105 name=subnet_name,
106 network_id=cls.vip_network_id,
107 cidr='10.100.1.0/24',
108 ip_version=4,
109 gateway_ip='10.100.1.1',
110 )
111 cls.vip_subnet_id = body['subnet']['id']
112 cls.addClassResourceCleanup(
113 test_utils.call_and_ignore_notfound_exc,
114 cls.subnets_client.delete_subnet,
115 cls.vip_subnet_id
116 )
117 cls.member_network_id = cls.vip_network_id
118 cls.member_subnet_id = cls.vip_subnet_id
119
120 if CONF.validation.connect_method == 'floating':
121 router_name = data_utils.rand_name(
122 'router',
123 prefix=cls.name_prefix
124 )
125 kwargs = {
126 'name': router_name,
127 'tenant_id': cls.tenant_id
128 }
129 if CONF.network.public_network_id:
130 kwargs['external_gateway_info'] = dict(
131 network_id=CONF.network.public_network_id
132 )
133 body = cls.routers_adm_client.create_router(**kwargs)
134 cls.router_id = body['router']['id']
135 cls.addClassResourceCleanup(
136 test_utils.call_and_ignore_notfound_exc,
137 cls.routers_adm_client.delete_router,
138 cls.router_id,
139 )
140
141 cls.routers_adm_client.add_router_interface(
142 cls.router_id, subnet_id=cls.member_subnet_id
143 )
144 cls.addClassResourceCleanup(
145 test_utils.call_and_ignore_notfound_exc,
146 cls.routers_adm_client.remove_router_interface,
147 cls.router_id,
148 subnet_id=cls.member_subnet_id
149 )
150 else:
151 cls.vip_network_id = CONF.loadbalancer.vip_network_id
152 cls.vip_subnet_id = CONF.loadbalancer.vip_subnet_id
153 cls.member_subnet_id = CONF.loadbalancer.premade_server_subnet_id
154
155 @tenacity.retry(
156 wait=tenacity.wait_fixed(CONF.loadbalancer.lb_build_interval),
157 stop=tenacity.stop_after_delay(CONF.loadbalancer.lb_build_timeout),
158 retry=tenacity.retry_if_exception_type(AssertionError)
159 )
160 def await_loadbalancer_active(self, id, name=None):
161 resp, body = self.lb_client.get_obj('loadbalancers', id)
162 self.assertEqual(200, resp.status)
163
164 lb = body['loadbalancer']
165
166 if lb['provisioning_status'] == 'ERROR':
167 raise Exception('Failed to wait for loadbalancer to be active, '
168 'actual provisioning_status: ERROR')
169
170 self.assertEqual('ACTIVE', lb['provisioning_status'])
171
172 if name:
173 self.assertEqual(name, lb['name'])
174
175 @tenacity.retry(
176 wait=tenacity.wait_fixed(CONF.loadbalancer.lb_build_interval),
177 stop=tenacity.stop_after_delay(CONF.loadbalancer.lb_build_timeout),
178 retry=tenacity.retry_if_exception_type(AssertionError)
179 )
180 def await_loadbalancer_deleted(self, id):
181 resp, body = self.lb_client.get_obj('loadbalancers', id)
182 self.assertEqual(200, resp.status)
183
184 lb = body['loadbalancer']
185 self.assertEqual('DELETED', lb['provisioning_status'])
186
187 @tenacity.retry(
188 wait=tenacity.wait_fixed(CONF.loadbalancer.lb_build_interval),
189 stop=tenacity.stop_after_delay(CONF.loadbalancer.lb_build_timeout),
190 retry=tenacity.retry_if_exception_type(AssertionError)
191 )
192 def await_listener_active(self, id, name=None):
193 resp, body = self.lb_client.get_obj('listeners', id)
194 self.assertEqual(200, resp.status)
195
196 listener = body['listener']
197
198 if listener['provisioning_status'] == 'ERROR':
199 raise Exception('Failed to wait for listener to be active, actual '
200 'provisioning_status: ERROR')
201
202 self.assertEqual('ACTIVE', listener['provisioning_status'])
203 self.assertEqual('ONLINE', listener['operating_status'])
204
205 if name:
206 self.assertEqual(name, listener['name'])
207
208 def create_loadbalancer(self, **kwargs):
209 name = data_utils.rand_name('lb', prefix=self.name_prefix)
210 payload = {'loadbalancer': {'name': name}}
211 payload['loadbalancer'].update(kwargs)
212
213 resp, body = self.lb_client.post_json('loadbalancers', payload)
214 self.assertEqual(201, resp.status)
215
216 lb = body['loadbalancer']
217 lb_id = lb['id']
218
219 self.addCleanup(self.delete_loadbalancer, lb_id, ignore_error=True)
220 LOG.info('Waiting for loadbalancer %s to be active', lb_id)
221 self.await_loadbalancer_active(
222 lb_id,
223 name=payload['loadbalancer']['name']
224 )
225
226 self.lb_id = lb['id']
227 self.vip_port = lb['vip_port_id']
228 if CONF.validation.connect_method == 'floating':
229 self.vip_address = self._associate_floatingip()
230 else:
231 self.vip_address = lb['vip_address']
232
233 return lb
234
235 def update_loadbalancer(self, lb_id, **kwargs):
236 new_name = data_utils.rand_name('lb', prefix=self.name_prefix)
237 payload = {'loadbalancer': {'name': new_name}}
238 payload['loadbalancer'].update(kwargs)
239
240 resp, _ = self.lb_client.put_json('loadbalancers', lb_id, payload)
241 self.assertEqual(200, resp.status)
242
243 # Wait for loadbalancer to be active
244 LOG.info(
245 'Waiting for loadbalancer %s to be active after update', lb_id
246 )
247 self.await_loadbalancer_active(lb_id)
248
249 def delete_loadbalancer(self, id, ignore_error=False):
250 """Delete loadbalancer and wait for it to be deleted.
251
252 Only if loadbalancer is deleted completely can other network resources
253 be deleted.
254 """
255 resp = self.lb_client.delete_resource('loadbalancers', id,
256 ignore_error=ignore_error,
257 cascade=True)
258 if resp:
259 self.assertEqual(204, resp.status)
260
261 LOG.info('Waiting for loadbalancer %s to be deleted', id)
262 self.await_loadbalancer_deleted(id)
263
264 def create_listener(self, lb_id, **kwargs):
265 name = data_utils.rand_name('listener', prefix=self.name_prefix)
266 payload = {
267 'listener': {
268 'protocol': 'HTTP',
269 'protocol_port': '80',
270 'loadbalancer_id': lb_id,
271 'name': name
272 }
273 }
274 payload['listener'].update(kwargs)
275
276 resp, body = self.lb_client.post_json('listeners', payload)
277 self.assertEqual(201, resp.status)
278
279 listener_id = body['listener']['id']
280
281 LOG.info(
282 'Waiting for loadbalancer %s to be active after listener %s '
283 'creation', lb_id, listener_id
284 )
285 self.addCleanup(self.delete_listener, listener_id, lb_id,
286 ignore_error=True)
287 self.await_loadbalancer_active(lb_id)
288
289 return body['listener']
290
291 def update_listener(self, listener_id, lb_id, **kwargs):
292 new_name = data_utils.rand_name('listener', prefix=self.name_prefix)
293 payload = {'listener': {'name': new_name}}
294 payload['listener'].update(kwargs)
295
296 resp, _ = self.lb_client.put_json('listeners', listener_id, payload)
297 self.assertEqual(200, resp.status)
298
299 # Wait for loadbalancer to be active
300 LOG.info(
301 'Waiting for loadbalancer %s to be active after listener %s '
302 'update', lb_id, listener_id
303 )
304 self.await_loadbalancer_active(lb_id)
305
306 def delete_listener(self, id, lb_id, ignore_error=False):
307 resp = self.lb_client.delete_resource('listeners', id,
308 ignore_error=ignore_error)
309 if resp:
310 self.assertEqual(204, resp.status)
311
312 LOG.info(
313 'Waiting for loadbalancer %s to be active after deleting '
314 'listener %s', lb_id, id
315 )
316 self.await_loadbalancer_active(lb_id)
317
318 def create_pool(self, lb_id, **kwargs):
319 name = data_utils.rand_name('pool', prefix=self.name_prefix)
320 payload = {
321 'pool': {
322 'name': name,
323 'loadbalancer_id': lb_id,
324 'lb_algorithm': 'ROUND_ROBIN',
325 'protocol': 'HTTP'
326 }
327 }
328 payload['pool'].update(kwargs)
329
330 resp, body = self.lb_client.post_json('pools', payload)
331 self.assertEqual(201, resp.status)
332
333 pool_id = body['pool']['id']
334
335 LOG.info(
336 'Waiting for loadbalancer %s to be active after pool %s creation',
337 lb_id, pool_id
338 )
339 self.addCleanup(self.delete_pool, pool_id, lb_id, ignore_error=True)
340 self.await_loadbalancer_active(lb_id)
341
342 return body['pool']
343
344 def update_pool(self, pool_id, lb_id, **kwargs):
345 new_name = data_utils.rand_name('pool', prefix=self.name_prefix)
346 payload = {'pool': {'name': new_name}}
347 payload['pool'].update(kwargs)
348
349 resp, _ = self.lb_client.put_json('pools', pool_id, payload)
350 self.assertEqual(200, resp.status)
351
352 # Wait for loadbalancer to be active
353 LOG.info(
354 'Waiting for loadbalancer %s to be active after pool %s update',
355 lb_id, pool_id
356 )
357 self.await_loadbalancer_active(lb_id)
358
359 def delete_pool(self, id, lb_id, ignore_error=False):
360 resp = self.lb_client.delete_resource('pools', id,
361 ignore_error=ignore_error)
362 if resp:
363 self.assertEqual(204, resp.status)
364
365 LOG.info(
366 'Waiting for loadbalancer %s to be active after deleting '
367 'pool %s', lb_id, id
368 )
369 self.await_loadbalancer_active(lb_id)
370
371 def create_member(self, pool_id, lb_id, **kwargs):
372 name = data_utils.rand_name('member', prefix=self.name_prefix)
373 payload = {'member': {'name': name}}
374 payload['member'].update(kwargs)
375
376 resp, body = self.lb_client.post_json(
377 'pools/%s/members' % pool_id, payload
378 )
379 self.assertEqual(201, resp.status)
380
381 member_id = body['member']['id']
382
383 LOG.info(
384 'Waiting for loadbalancer %s to be active after adding '
385 'member %s', lb_id, member_id
386 )
387 self.addCleanup(self.delete_member, member_id, pool_id,
388 lb_id, ignore_error=True)
389 self.await_loadbalancer_active(lb_id)
390
391 return body['member']
392
393 def delete_member(self, id, pool_id, lb_id, ignore_error=False):
394 resp = self.lb_client.delete_resource(
395 'pools/%s/members' % pool_id,
396 id,
397 ignore_error=ignore_error
398 )
399 if resp:
400 self.assertEqual(204, resp.status)
401
402 LOG.info(
403 'Waiting for loadbalancer %s to be active after deleting '
404 'member %s', lb_id, id
405 )
406 self.await_loadbalancer_active(lb_id)
407
408 def _wait_for_lb_functional(self, vip_address):
409 session = requests.Session()
410 start = time.time()
411
412 while time.time() - start < CONF.loadbalancer.lb_build_timeout:
413 try:
414 session.get("http://{0}".format(vip_address), timeout=2)
415 time.sleep(1)
416 return
417 except Exception:
418 LOG.warning('Server is not passing initial traffic. Waiting.')
419 time.sleep(1)
420 LOG.error('Server did not begin passing traffic within the timeout '
421 'period. Failing test.')
422 raise lib_exc.ServerFault()
423
424 def check_members_balanced(self):
425 session = requests.Session()
426 response_counts = {}
427
428 self._wait_for_lb_functional(self.vip_address)
429
430 # Send a number requests to lb vip
431 for i in range(20):
432 try:
433 r = session.get('http://{0}'.format(self.vip_address),
434 timeout=2)
435 LOG.debug('Loadbalancer response: %s', r.content)
436
437 if r.content in response_counts:
438 response_counts[r.content] += 1
439 else:
440 response_counts[r.content] = 1
441
442 except Exception:
443 LOG.exception('Failed to send request to loadbalancer vip')
444 raise lib_exc.BadRequest(message='Failed to connect to lb')
445
446 # Ensure the correct number of members
447 self.assertEqual(2, len(response_counts))
448
449 # Ensure both members got the same number of responses
450 self.assertEqual(1, len(set(response_counts.values())))
451
452 def _delete_floatingip(self, floating_ip):
Lingxian Kongbf966a92018-01-16 00:20:37 +1300453 self.floatingip_client.update_floatingip(
Jude Cross638c4ef2017-07-24 14:57:20 -0700454 floating_ip,
455 port_id=None
456 )
457 test_utils.call_and_ignore_notfound_exc(
Lingxian Kongbf966a92018-01-16 00:20:37 +1300458 self.floatingip_client.delete_floatingip, floating_ip
Jude Cross638c4ef2017-07-24 14:57:20 -0700459 )
460
461 def _associate_floatingip(self):
462 # Associate floatingip with loadbalancer vip
Lingxian Kongbf966a92018-01-16 00:20:37 +1300463 floatingip = self.floatingip_client.create_floatingip(
Jude Cross638c4ef2017-07-24 14:57:20 -0700464 floating_network_id=CONF.network.public_network_id
465 )['floatingip']
466 floatip_vip = floatingip['floating_ip_address']
467 self.addCleanup(self._delete_floatingip, floatingip['id'])
468
469 LOG.debug('Floating ip %s created.', floatip_vip)
470
Lingxian Kongbf966a92018-01-16 00:20:37 +1300471 self.floatingip_client.update_floatingip(
Jude Cross638c4ef2017-07-24 14:57:20 -0700472 floatingip['id'],
473 port_id=self.vip_port
474 )
475
476 LOG.debug('Floating ip %s associated with vip.', floatip_vip)
477 return floatip_vip
478
479 def create_backend(self):
480 if CONF.loadbalancer.premade_server_ip:
481 self.vm_ip = CONF.loadbalancer.premade_server_ip
482 return
483
484 vr_resources = self.vr.resources
485 vm = server_util.create_server(
486 self.os_roles_lbmember,
487 validatable=True,
488 validation_resources=vr_resources,
489 wait_until='ACTIVE',
490 tenant_network=({'id': self.member_network_id}
491 if self.member_network_id else None),
492 )
493 self.addCleanup(
494 server_util.clear_server,
495 self.os_roles_lbmember.servers_client,
496 vm['id']
497 )
498
499 # Get vm private ip address.
500 ifaces = self.interfaces_client.list_interfaces(vm['id'])
501 for iface in ifaces['interfaceAttachments']:
502 if not self.member_network_id or (iface['net_id'] ==
503 self.vip_network_id):
504 for ip_info in iface['fixed_ips']:
505 if not self.vip_subnet_id or (ip_info['subnet_id'] ==
506 self.vip_subnet_id):
507 self.vm_ip = ip_info['ip_address']
508 break
509 if self.vm_ip:
510 break
511
512 self.assertIsNotNone(self.vm_ip)
513
514 if CONF.validation.connect_method == 'floating':
515 connect_ip = vr_resources['floating_ip']['floating_ip_address']
516 else:
517 connect_ip = self.vm_ip
518
519 server_util.run_webserver(
520 connect_ip,
521 vr_resources['keypair']['private_key']
522 )
523 LOG.debug('Web servers are running inside %s', vm['id'])