# Copyright 2017 GoDaddy # Copyright 2017 Catalyst IT Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import time from oslo_log import log as logging import requests from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib.common.utils import test_utils from tempest.lib import exceptions as lib_exc from tempest import test import tenacity from octavia_tempest_plugin.tests import server_util CONF = config.CONF LOG = logging.getLogger(__name__) class BaseLoadbalancerTest(test.BaseTestCase): credentials = (['lbmember', CONF.loadbalancer.member_role], 'admin') name_prefix = 'Tempest-BaseLoadbalancerTest' vip_network_id = None vip_subnet_id = None vip_address = None member_subnet_id = None member_network_id = None vm_ip = None @classmethod def skip_checks(cls): super(BaseLoadbalancerTest, cls).skip_checks() if not CONF.service_available.loadbalancer: raise cls.skipException("Loadbalancing service is not available.") service_list = { 'loadbalancing': CONF.service_available.loadbalancer, 'compute': CONF.service_available.nova, 'image': CONF.service_available.glance, 'neutron': CONF.service_available.neutron } for srv, available in service_list.items(): if not available: raise cls.skipException("Service %s is not available." % srv) @classmethod def setup_clients(cls): super(BaseLoadbalancerTest, cls).setup_clients() cls.lb_client = cls.os_roles_lbmember.octavia_v2.LoadbalancerClient() cls.servers_client = cls.os_roles_lbmember.servers_client cls.networks_client = cls.os_roles_lbmember.networks_client cls.subnets_client = cls.os_roles_lbmember.subnets_client cls.interfaces_client = cls.os_roles_lbmember.interfaces_client cls.sg_rule_client = cls.os_roles_lbmember.security_group_rules_client cls.floatingip_client = cls.os_roles_lbmember.floating_ips_client cls.routers_adm_client = cls.os_admin.routers_client if CONF.identity.auth_version == 'v3': project_id = cls.os_roles_lbmember.auth_provider.auth_data[1][ 'project']['id'] else: project_id = cls.os_roles_lbmember.auth_provider.auth_data[ 1]['token']['tenant']['id'] cls.tenant_id = project_id cls.user_id = cls.os_roles_lbmember.auth_provider.auth_data[1][ 'user']['id'] @classmethod def resource_setup(cls): """Creates network resources.""" super(BaseLoadbalancerTest, cls).resource_setup() if not CONF.loadbalancer.vip_network_id: network_name = data_utils.rand_name( 'network', prefix=cls.name_prefix ) body = cls.networks_client.create_network(name=network_name) cls.vip_network_id = body['network']['id'] cls.addClassResourceCleanup( test_utils.call_and_ignore_notfound_exc, cls.networks_client.delete_network, cls.vip_network_id ) subnet_name = data_utils.rand_name( 'subnet', prefix=cls.name_prefix ) body = cls.subnets_client.create_subnet( name=subnet_name, network_id=cls.vip_network_id, cidr='10.100.1.0/24', ip_version=4, gateway_ip='10.100.1.1', ) cls.vip_subnet_id = body['subnet']['id'] cls.addClassResourceCleanup( test_utils.call_and_ignore_notfound_exc, cls.subnets_client.delete_subnet, cls.vip_subnet_id ) cls.member_network_id = cls.vip_network_id cls.member_subnet_id = cls.vip_subnet_id if CONF.validation.connect_method == 'floating': router_name = data_utils.rand_name( 'router', prefix=cls.name_prefix ) kwargs = { 'name': router_name, 'tenant_id': cls.tenant_id } if CONF.network.public_network_id: kwargs['external_gateway_info'] = dict( network_id=CONF.network.public_network_id ) body = cls.routers_adm_client.create_router(**kwargs) cls.router_id = body['router']['id'] cls.addClassResourceCleanup( test_utils.call_and_ignore_notfound_exc, cls.routers_adm_client.delete_router, cls.router_id, ) cls.routers_adm_client.add_router_interface( cls.router_id, subnet_id=cls.member_subnet_id ) cls.addClassResourceCleanup( test_utils.call_and_ignore_notfound_exc, cls.routers_adm_client.remove_router_interface, cls.router_id, subnet_id=cls.member_subnet_id ) else: cls.vip_network_id = CONF.loadbalancer.vip_network_id cls.vip_subnet_id = CONF.loadbalancer.vip_subnet_id cls.member_subnet_id = CONF.loadbalancer.premade_server_subnet_id @tenacity.retry( wait=tenacity.wait_fixed(CONF.loadbalancer.lb_build_interval), stop=tenacity.stop_after_delay(CONF.loadbalancer.lb_build_timeout), retry=tenacity.retry_if_exception_type(AssertionError) ) def await_loadbalancer_active(self, id, name=None): resp, body = self.lb_client.get_obj('loadbalancers', id) self.assertEqual(200, resp.status) lb = body['loadbalancer'] if lb['provisioning_status'] == 'ERROR': raise Exception('Failed to wait for loadbalancer to be active, ' 'actual provisioning_status: ERROR') self.assertEqual('ACTIVE', lb['provisioning_status']) if name: self.assertEqual(name, lb['name']) @tenacity.retry( wait=tenacity.wait_fixed(CONF.loadbalancer.lb_build_interval), stop=tenacity.stop_after_delay(CONF.loadbalancer.lb_build_timeout), retry=tenacity.retry_if_exception_type(AssertionError) ) def await_loadbalancer_deleted(self, id): resp, body = self.lb_client.get_obj('loadbalancers', id) self.assertEqual(200, resp.status) lb = body['loadbalancer'] self.assertEqual('DELETED', lb['provisioning_status']) @tenacity.retry( wait=tenacity.wait_fixed(CONF.loadbalancer.lb_build_interval), stop=tenacity.stop_after_delay(CONF.loadbalancer.lb_build_timeout), retry=tenacity.retry_if_exception_type(AssertionError) ) def await_listener_active(self, id, name=None): resp, body = self.lb_client.get_obj('listeners', id) self.assertEqual(200, resp.status) listener = body['listener'] if listener['provisioning_status'] == 'ERROR': raise Exception('Failed to wait for listener to be active, actual ' 'provisioning_status: ERROR') self.assertEqual('ACTIVE', listener['provisioning_status']) self.assertEqual('ONLINE', listener['operating_status']) if name: self.assertEqual(name, listener['name']) def create_loadbalancer(self, **kwargs): name = data_utils.rand_name('lb', prefix=self.name_prefix) payload = {'loadbalancer': {'name': name}} payload['loadbalancer'].update(kwargs) resp, body = self.lb_client.post_json('loadbalancers', payload) self.assertEqual(201, resp.status) lb = body['loadbalancer'] lb_id = lb['id'] self.addCleanup(self.delete_loadbalancer, lb_id, ignore_error=True) LOG.info('Waiting for loadbalancer %s to be active', lb_id) self.await_loadbalancer_active( lb_id, name=payload['loadbalancer']['name'] ) self.lb_id = lb['id'] self.vip_port = lb['vip_port_id'] if CONF.validation.connect_method == 'floating': self.vip_address = self._associate_floatingip() else: self.vip_address = lb['vip_address'] return lb def update_loadbalancer(self, lb_id, **kwargs): new_name = data_utils.rand_name('lb', prefix=self.name_prefix) payload = {'loadbalancer': {'name': new_name}} payload['loadbalancer'].update(kwargs) resp, _ = self.lb_client.put_json('loadbalancers', lb_id, payload) self.assertEqual(200, resp.status) # Wait for loadbalancer to be active LOG.info( 'Waiting for loadbalancer %s to be active after update', lb_id ) self.await_loadbalancer_active(lb_id) def delete_loadbalancer(self, id, ignore_error=False): """Delete loadbalancer and wait for it to be deleted. Only if loadbalancer is deleted completely can other network resources be deleted. """ resp = self.lb_client.delete_resource('loadbalancers', id, ignore_error=ignore_error, cascade=True) if resp: self.assertEqual(204, resp.status) LOG.info('Waiting for loadbalancer %s to be deleted', id) self.await_loadbalancer_deleted(id) def create_listener(self, lb_id, **kwargs): name = data_utils.rand_name('listener', prefix=self.name_prefix) payload = { 'listener': { 'protocol': 'HTTP', 'protocol_port': '80', 'loadbalancer_id': lb_id, 'name': name } } payload['listener'].update(kwargs) resp, body = self.lb_client.post_json('listeners', payload) self.assertEqual(201, resp.status) listener_id = body['listener']['id'] LOG.info( 'Waiting for loadbalancer %s to be active after listener %s ' 'creation', lb_id, listener_id ) self.addCleanup(self.delete_listener, listener_id, lb_id, ignore_error=True) self.await_loadbalancer_active(lb_id) return body['listener'] def update_listener(self, listener_id, lb_id, **kwargs): new_name = data_utils.rand_name('listener', prefix=self.name_prefix) payload = {'listener': {'name': new_name}} payload['listener'].update(kwargs) resp, _ = self.lb_client.put_json('listeners', listener_id, payload) self.assertEqual(200, resp.status) # Wait for loadbalancer to be active LOG.info( 'Waiting for loadbalancer %s to be active after listener %s ' 'update', lb_id, listener_id ) self.await_loadbalancer_active(lb_id) def delete_listener(self, id, lb_id, ignore_error=False): resp = self.lb_client.delete_resource('listeners', id, ignore_error=ignore_error) if resp: self.assertEqual(204, resp.status) LOG.info( 'Waiting for loadbalancer %s to be active after deleting ' 'listener %s', lb_id, id ) self.await_loadbalancer_active(lb_id) def create_pool(self, lb_id, **kwargs): name = data_utils.rand_name('pool', prefix=self.name_prefix) payload = { 'pool': { 'name': name, 'loadbalancer_id': lb_id, 'lb_algorithm': 'ROUND_ROBIN', 'protocol': 'HTTP' } } payload['pool'].update(kwargs) resp, body = self.lb_client.post_json('pools', payload) self.assertEqual(201, resp.status) pool_id = body['pool']['id'] LOG.info( 'Waiting for loadbalancer %s to be active after pool %s creation', lb_id, pool_id ) self.addCleanup(self.delete_pool, pool_id, lb_id, ignore_error=True) self.await_loadbalancer_active(lb_id) return body['pool'] def update_pool(self, pool_id, lb_id, **kwargs): new_name = data_utils.rand_name('pool', prefix=self.name_prefix) payload = {'pool': {'name': new_name}} payload['pool'].update(kwargs) resp, _ = self.lb_client.put_json('pools', pool_id, payload) self.assertEqual(200, resp.status) # Wait for loadbalancer to be active LOG.info( 'Waiting for loadbalancer %s to be active after pool %s update', lb_id, pool_id ) self.await_loadbalancer_active(lb_id) def delete_pool(self, id, lb_id, ignore_error=False): resp = self.lb_client.delete_resource('pools', id, ignore_error=ignore_error) if resp: self.assertEqual(204, resp.status) LOG.info( 'Waiting for loadbalancer %s to be active after deleting ' 'pool %s', lb_id, id ) self.await_loadbalancer_active(lb_id) def create_member(self, pool_id, lb_id, **kwargs): name = data_utils.rand_name('member', prefix=self.name_prefix) payload = {'member': {'name': name}} payload['member'].update(kwargs) resp, body = self.lb_client.post_json( 'pools/%s/members' % pool_id, payload ) self.assertEqual(201, resp.status) member_id = body['member']['id'] LOG.info( 'Waiting for loadbalancer %s to be active after adding ' 'member %s', lb_id, member_id ) self.addCleanup(self.delete_member, member_id, pool_id, lb_id, ignore_error=True) self.await_loadbalancer_active(lb_id) return body['member'] def delete_member(self, id, pool_id, lb_id, ignore_error=False): resp = self.lb_client.delete_resource( 'pools/%s/members' % pool_id, id, ignore_error=ignore_error ) if resp: self.assertEqual(204, resp.status) LOG.info( 'Waiting for loadbalancer %s to be active after deleting ' 'member %s', lb_id, id ) self.await_loadbalancer_active(lb_id) def _wait_for_lb_functional(self, vip_address): session = requests.Session() start = time.time() while time.time() - start < CONF.loadbalancer.lb_build_timeout: try: session.get("http://{0}".format(vip_address), timeout=2) time.sleep(1) return except Exception: LOG.warning('Server is not passing initial traffic. Waiting.') time.sleep(1) LOG.error('Server did not begin passing traffic within the timeout ' 'period. Failing test.') raise lib_exc.ServerFault() def check_members_balanced(self): session = requests.Session() response_counts = {} self._wait_for_lb_functional(self.vip_address) # Send a number requests to lb vip for i in range(20): try: r = session.get('http://{0}'.format(self.vip_address), timeout=2) LOG.debug('Loadbalancer response: %s', r.content) if r.content in response_counts: response_counts[r.content] += 1 else: response_counts[r.content] = 1 except Exception: LOG.exception('Failed to send request to loadbalancer vip') raise lib_exc.BadRequest(message='Failed to connect to lb') # Ensure the correct number of members self.assertEqual(2, len(response_counts)) # Ensure both members got the same number of responses self.assertEqual(1, len(set(response_counts.values()))) def _delete_floatingip(self, floating_ip): self.floatingip_client.update_floatingip( floating_ip, port_id=None ) test_utils.call_and_ignore_notfound_exc( self.floatingip_client.delete_floatingip, floating_ip ) def _associate_floatingip(self): # Associate floatingip with loadbalancer vip floatingip = self.floatingip_client.create_floatingip( floating_network_id=CONF.network.public_network_id )['floatingip'] floatip_vip = floatingip['floating_ip_address'] self.addCleanup(self._delete_floatingip, floatingip['id']) LOG.debug('Floating ip %s created.', floatip_vip) self.floatingip_client.update_floatingip( floatingip['id'], port_id=self.vip_port ) LOG.debug('Floating ip %s associated with vip.', floatip_vip) return floatip_vip def create_backend(self): if CONF.loadbalancer.premade_server_ip: self.vm_ip = CONF.loadbalancer.premade_server_ip return vr_resources = self.vr.resources vm = server_util.create_server( self.os_roles_lbmember, validatable=True, validation_resources=vr_resources, wait_until='ACTIVE', tenant_network=({'id': self.member_network_id} if self.member_network_id else None), ) self.addCleanup( server_util.clear_server, self.os_roles_lbmember.servers_client, vm['id'] ) # Get vm private ip address. ifaces = self.interfaces_client.list_interfaces(vm['id']) for iface in ifaces['interfaceAttachments']: if not self.member_network_id or (iface['net_id'] == self.vip_network_id): for ip_info in iface['fixed_ips']: if not self.vip_subnet_id or (ip_info['subnet_id'] == self.vip_subnet_id): self.vm_ip = ip_info['ip_address'] break if self.vm_ip: break self.assertIsNotNone(self.vm_ip) if CONF.validation.connect_method == 'floating': connect_ip = vr_resources['floating_ip']['floating_ip_address'] else: connect_ip = self.vm_ip server_util.run_webserver( connect_ip, vr_resources['keypair']['private_key'] ) LOG.debug('Web servers are running inside %s', vm['id'])