diff --git a/plugin_test/vapor/vapor/conftest.py b/plugin_test/vapor/vapor/conftest.py index 7210a766f..a40e1fa37 100644 --- a/plugin_test/vapor/vapor/conftest.py +++ b/plugin_test/vapor/vapor/conftest.py @@ -13,6 +13,7 @@ from vapor.fixtures.dns import * # noqa from vapor.fixtures.images import * # noqa from vapor.fixtures.instance_ip import * # noqa from vapor.fixtures.ipams import * # noqa +from vapor.fixtures.lbaas import * # noqa from vapor.fixtures.networks import * # noqa from vapor.fixtures.nodes import * # noqa from vapor.fixtures.policies import * # noqa diff --git a/plugin_test/vapor/vapor/fixtures/lbaas.py b/plugin_test/vapor/vapor/fixtures/lbaas.py new file mode 100644 index 000000000..9a25b9b80 --- /dev/null +++ b/plugin_test/vapor/vapor/fixtures/lbaas.py @@ -0,0 +1,58 @@ +# 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 functools + +import pytest +from stepler.third_party import utils + +from vapor.helpers import lbaas + + +@pytest.fixture +def lbaas_steps(neutron_client): + return lbaas.LBaaSSteps(neutron_client) + + +@pytest.fixture +def loadbalancer(request, lbaas_steps, net_subnet_router): + """Fixture to create loadbalancer on default subnet.""" + name = next(utils.generate_ids('lb')) + request.addfinalizer( + functools.partial(lbaas_steps.cleanup_lbs, names=[name])) + return lbaas_steps.create_lb(name, net_subnet_router[1]) + + +@pytest.fixture +def lb_listener(request, lbaas_steps, loadbalancer): + """Fixture to create lbaas HTTP listener.""" + name = next(utils.generate_ids('lb_listener')) + request.addfinalizer( + functools.partial(lbaas_steps.cleanup_listeners, names=[name])) + return lbaas_steps.create_listener( + name=name, + loadbalancer=loadbalancer, + protocol="HTTP", + protocol_port=80) + + +@pytest.fixture +def lb_pool(request, lbaas_steps, lb_listener): + """Fixture to create lbaas pool.""" + name = next(utils.generate_ids('lb_pool')) + request.addfinalizer( + functools.partial(lbaas_steps.cleanup_pools, names=[name])) + return lbaas_steps.create_pool( + name=name, + listener=lb_listener, + protocol="HTTP", + lb_algorithm="ROUND_ROBIN") diff --git a/plugin_test/vapor/vapor/helpers/lbaas.py b/plugin_test/vapor/vapor/helpers/lbaas.py new file mode 100644 index 000000000..436d6edfc --- /dev/null +++ b/plugin_test/vapor/vapor/helpers/lbaas.py @@ -0,0 +1,191 @@ +# 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. + +from hamcrest import (assert_that, equal_to, is_, is_not, contains, + has_entries, starts_with, has_length) # noqa: H310 +import requests +from stepler import base +from stepler.third_party import waiter + +from vapor import settings + + +class LBaaSSteps(base.BaseSteps): + """LBaaS steps.""" + + def _check_presence(self, objs, list_method, expected_presence, timeout=0): + def _check_presence(): + all_objs = list_method() + matcher = is_ + if not expected_presence: + matcher = is_not + return waiter.expect_that( + all_objs, + matcher( + contains(*[has_entries(id=obj['id']) for obj in objs]))) + + waiter.wait(_check_presence, timeout_seconds=timeout) + + def create_lb(self, name, subnet, **kwargs): + """Create loadbalancer and wait it became to online.""" + loadbalancer = self._client.lbaas_loadbalancers.create( + name=name, vip_subnet_id=subnet['id'], **kwargs) + + loadbalancer = self.check_lb_provisioning_status( + loadbalancer, timeout=settings.LBAAS_ONLINE_TIMEOUT) + + return loadbalancer + + def delete_lbs(self, loadbalancers): + """Delete loadbalancer and wait for deletion to be completed.""" + for loadbalancer in loadbalancers: + self._client.lbaas_loadbalancers.delete(loadbalancer['id']) + + self.check_lbs_presence( + loadbalancers, timeout=settings.LBAAS_DELETE_TIMEOUT) + + def check_lb_provisioning_status(self, + loadbalancer, + expected_status="ACTIVE", + timeout=0): + """Check that loadbalancer has expected provisioning status.""" + + def _check_status(): + lb = self._client.lbaas_loadbalancers.get(loadbalancer['id']) + waiter.expect_that(lb['provisioning_status'], + is_not(starts_with('PENDING_'))) + return lb + + loadbalancer = waiter.wait(_check_status, timeout_seconds=timeout) + assert_that(loadbalancer['provisioning_status'], + equal_to(expected_status)) + return loadbalancer + + def check_lbs_presence(self, + loadbalancers, + expected_presence=True, + timeout=0): + """Check that loadbalancer is present (or not).""" + self._check_presence( + loadbalancers, + self._client.lbaas_loadbalancers.list, + expected_presence, + timeout=timeout) + + def cleanup_lbs(self, names): + """Remove all loadbalancers by names list.""" + loadbalancers = [] + for name in names: + for loadbalancer in self._client.lbaas_loadbalancers.find_all( + name=name): + loadbalancers.append(loadbalancer) + self._client.lbaas_loadbalancers.delete(loadbalancer['id']) + + self.check_lbs_presence( + loadbalancers, + expected_presence=False, + timeout=settings.LBAAS_DELETE_TIMEOUT) + + def create_listener(self, name, loadbalancer, protocol, protocol_port, + **kwargs): + """Create LBaaS listener.""" + listener = self._client.lbaas_listeners.create( + name=name, + loadbalancer_id=loadbalancer['id'], + protocol=protocol, + protocol_port=protocol_port, + **kwargs) + + self.check_lb_provisioning_status( + loadbalancer, timeout=settings.LBAAS_ONLINE_TIMEOUT) + + return listener + + def delete_listener(self, listener): + """Delete LBaaS listener.""" + listener = self._client.lbaas_listeners.get(listener['id']) + loadbalancers = listener['loadbalancers'] + self._client.lbaas_listeners.delete(listener['id']) + for lb in loadbalancers: + self.check_lb_provisioning_status( + lb, timeout=settings.LBAAS_ONLINE_TIMEOUT) + + def cleanup_listeners(self, names): + """Remove all listeners by names list.""" + for name in names: + for listener in self._client.lbaas_listeners.find_all(name=name): + self.delete_listener(listener) + + def create_pool(self, name, listener, protocol, lb_algorithm, **kwargs): + """Create LBaaS pool.""" + pool = self._client.lbaas_pools.create( + name=name, + listener_id=listener['id'], + protocol=protocol, + lb_algorithm=lb_algorithm, + **kwargs) + + for loadbalancer in pool['loadbalancers']: + self.check_lb_provisioning_status( + loadbalancer, timeout=settings.LBAAS_ONLINE_TIMEOUT) + + return pool + + def delete_pool(self, pool): + """Create LBaaS pool.""" + self._client.lbaas_pools.delete(pool['id']) + for loadbalancer in pool['loadbalancers']: + self.check_lb_provisioning_status( + loadbalancer, timeout=settings.LBAAS_ONLINE_TIMEOUT) + + def cleanup_pools(self, names): + """Remove all pools by names list.""" + loadbalancers = [] + for name in names: + for pool in self._client.lbaas_pools.find_all(name=name): + self._client.lbaas_pools.delete(pool['id']) + loadbalancers.extend(pool['loadbalancers']) + + for loadbalancer in loadbalancers: + self.check_lb_provisioning_status( + loadbalancer, timeout=settings.LBAAS_ONLINE_TIMEOUT) + + def create_member(self, pool, address, protocol_port, subnet, **kwargs): + """Create LBaaS pool member.""" + member = pool.members.create( + address=address, + protocol_port=protocol_port, + subnet_id=subnet['id'], + **kwargs) + + for loadbalancer in pool['loadbalancers']: + self.check_lb_provisioning_status( + loadbalancer, timeout=settings.LBAAS_ONLINE_TIMEOUT) + + return member + + def delete_member(self, pool, member): + """Delete LBaaS pool member.""" + pool.members.delete(member['id']) + + for loadbalancer in pool['loadbalancers']: + self.check_lb_provisioning_status( + loadbalancer, timeout=settings.LBAAS_ONLINE_TIMEOUT) + + def check_balancing(self, ip, port, expected_count): + """Check that responses contains `expected_counts` variants.""" + responses = set() + for _ in range(expected_count * 3): + r = requests.get("http://{}:{}/".format(ip, port)) + r.raise_for_status() + responses.add(r.text) + assert_that(responses, has_length(expected_count)) diff --git a/plugin_test/vapor/vapor/settings.py b/plugin_test/vapor/vapor/settings.py index 91455faa6..779b51c25 100644 --- a/plugin_test/vapor/vapor/settings.py +++ b/plugin_test/vapor/vapor/settings.py @@ -221,12 +221,19 @@ SECURITY_GROUP_SSH_PING_RULES = (stepler_config.SECURITY_GROUP_SSH_RULES + DPDK_ENABLED_GROUP = u'Network devices using DPDK-compatible driver' - - # Service chaining # TODO(gdyuldin): relace with real URL NAT_SERVICE_IMAGE_URL = os.environ.get('NAT_SERVICE_IMAGE_URL', '/home/jenkins/nat.qcow2') SERVICE_INSTANCE_CREATE_TIMEOUT = 2 * 60 SERVICE_INSTANCE_BOOT_TIMEOUT = 10 * 60 -SERVICE_INSTANCE_BOOT_DONE_PATTERN = 'Cloud-init .+ finished' \ No newline at end of file +SERVICE_INSTANCE_BOOT_DONE_PATTERN = 'Cloud-init .+ finished' + +# LBAAS +LBAAS_ONLINE_TIMEOUT = 5 * 60 +LBAAS_DELETE_TIMEOUT = 2 * 60 + +HTTP_SERVER_CMD = ( + 'while true; do ' + 'echo -e "HTTP/1.0 200 OK\\r\\nContent-Length: $(hostname | wc -c)\\r\\n\\r\\n$(hostname)" | nc -l -p {port}; ' # noqa + 'done') \ No newline at end of file diff --git a/plugin_test/vapor/vapor/tests/test_dpdk.py b/plugin_test/vapor/vapor/tests/test_dpdk.py index fc9591741..191dc5563 100644 --- a/plugin_test/vapor/vapor/tests/test_dpdk.py +++ b/plugin_test/vapor/vapor/tests/test_dpdk.py @@ -70,7 +70,8 @@ def test_contrail_vrouter_dpdk_cpu_usage(os_faults_steps, computes): @pytest.mark.parametrize( - 'flavor', [dict(ram=1024, metadata={"hw:mem_page_size": "large"})], indirect=True) + 'flavor', [dict(ram=1024, metadata={"hw:mem_page_size": "large"})], + indirect=True) @pytest.mark.usefixtures('flavor') def test_vrouter_create_interface(request, os_faults_steps, computes): """Verify if vRouter creates interface after creation of a virtual machine. diff --git a/plugin_test/vapor/vapor/tests/test_lbaas.py b/plugin_test/vapor/vapor/tests/test_lbaas.py new file mode 100644 index 000000000..5d029e5d7 --- /dev/null +++ b/plugin_test/vapor/vapor/tests/test_lbaas.py @@ -0,0 +1,83 @@ +# 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. + +from six import moves +from stepler import config as stepler_config + +from vapor import settings + + +def test_loadbalancer_after_deleting_server( + flavor, cirros_image, net_subnet_router, security_group, loadbalancer, + create_floating_ip, port_steps, server_steps, lb_listener, lb_pool, + neutron_security_group_rule_steps, lbaas_steps): + """Check loadbalancer after deleting server. + + Steps: + #. Create network, subnet + #. Create loadbalancer + #. Create lbaas listener + #. Create lbaas pool + #. Create security group with allow ping rule + #. Create floating IP on loadbalancer port + #. Create 2 servers with simple HTTP server + #. Add servers' IPs to loadbalancer pool + #. Check that loadbalancer routes HTTP queries between servers + #. Delete one of servers + #. Check that loadbalancer pass all HTTP queries to single alive server + """ + port = 80 + neutron_security_group_rule_steps.add_rule_to_group( + security_group['id'], + direction='ingress', + protocol='tcp', + port_range_min=port, + port_range_max=port) + + http_server_cmd = settings.HTTP_SERVER_CMD.format(port=port) + + userdata = "\n".join([ + '#!/bin/sh -v', + 'screen -dmS daemon /bin/sh -c {cmd}', + 'screen -ls', + 'echo {done_marker}', + ]).format( + cmd=moves.shlex_quote(http_server_cmd), + done_marker=stepler_config.USERDATA_DONE_MARKER) + network, subnet, _ = net_subnet_router + servers = server_steps.create_servers( + count=2, + image=cirros_image, + flavor=flavor, + networks=[network], + security_groups=[security_group], + userdata=userdata) + for server in servers: + server_steps.check_server_log_contains_record( + server, + stepler_config.USERDATA_DONE_MARKER, + timeout=stepler_config.USERDATA_EXECUTING_TIMEOUT) + + ip = server_steps.get_fixed_ip(server) + lbaas_steps.create_member(lb_pool, ip, port, subnet) + + vip_port = {'id': loadbalancer['vip_port_id']} + port_steps.update(vip_port, security_groups=[security_group['id']]) + floating_ip = create_floating_ip(port=vip_port) + + lbaas_steps.check_balancing( + floating_ip['floating_ip_address'], port, expected_count=2) + + server_steps.delete_servers(servers[1:]) + + lbaas_steps.check_balancing( + floating_ip['floating_ip_address'], port, expected_count=1)