diff --git a/.zuul.yaml b/.zuul.yaml index ddea34fb1..ea70edc9d 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -24,3 +24,35 @@ - openstack/neutron-dynamic-routing - openstack/networking-l2gw - openstack/tap-as-a-service + +- job: + name: neutron-fwaas-fullstack + parent: legacy-dsvm-base + run: playbooks/neutron-fwaas-fullstack/run.yaml + post-run: playbooks/neutron-fwaas-fullstack/post.yaml + timeout: 7800 + required-projects: + - openstack-infra/devstack-gate + - openstack/neutron + irrelevant-files: + - ^.*\.rst$ + - ^doc/.*$ + - ^neutron/locale/.*$ + - ^releasenotes/.*$ + + +- job: + name: neutron-fwaas-fullstack-python35 + parent: legacy-dsvm-base + run: playbooks/legacy/neutron-fullstack-python35/run.yaml + post-run: playbooks/legacy/neutron-fullstack-python35/post.yaml + timeout: 7800 + required-projects: + - openstack-infra/devstack-gate + - openstack/neutron + irrelevant-files: + - ^.*\.rst$ + - ^doc/.*$ + - ^neutron/locale/.*$ + - ^releasenotes/.*$ + voting: false diff --git a/neutron_fwaas/tests/contrib/README b/neutron_fwaas/tests/contrib/README index 78dbe5583..a73d75af9 100644 --- a/neutron_fwaas/tests/contrib/README +++ b/neutron_fwaas/tests/contrib/README @@ -1,3 +1,3 @@ The files in this directory are intended for use by the -infra jobs that run the various functional test -suite in the gate for the neutron-fwaas repo. +Neutron infra jobs that run the various functional test +suites in the gate. diff --git a/neutron_fwaas/tests/contrib/functional-testing.filters b/neutron_fwaas/tests/contrib/functional-testing.filters index d9ed1db7c..23fbc9b73 100644 --- a/neutron_fwaas/tests/contrib/functional-testing.filters +++ b/neutron_fwaas/tests/contrib/functional-testing.filters @@ -4,4 +4,7 @@ # This file should be owned by (and only-writeable by) the root user [Filters] -#none currently +# enable ping from namespace +ping_filter: CommandFilter, ping, root +ping6_filter: CommandFilter, ping6, root +ping_kill: KillFilter, root, ping, -2 diff --git a/neutron_fwaas/tests/contrib/gate_hook.sh b/neutron_fwaas/tests/contrib/gate_hook.sh old mode 100755 new mode 100644 diff --git a/neutron_fwaas/tests/contrib/hooks/iptables_verify b/neutron_fwaas/tests/contrib/hooks/iptables_verify new file mode 100644 index 000000000..72cbd1aeb --- /dev/null +++ b/neutron_fwaas/tests/contrib/hooks/iptables_verify @@ -0,0 +1,4 @@ +[[post-config|/etc/neutron/neutron.conf]] + +[AGENT] +debug_iptables_rules=True diff --git a/neutron_fwaas/tests/contrib/post_test_hook.sh b/neutron_fwaas/tests/contrib/post_test_hook.sh old mode 100755 new mode 100644 index abb6a7846..bc7c76722 --- a/neutron_fwaas/tests/contrib/post_test_hook.sh +++ b/neutron_fwaas/tests/contrib/post_test_hook.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -xe @@ -7,12 +7,14 @@ NEUTRON_DIR="$BASE/new/neutron" TEMPEST_DIR="$BASE/new/tempest" SCRIPTS_DIR="/usr/os-testr-env/bin" +venv=${1:-"dsvm-functional"} + function generate_testr_results { # Give job user rights to access tox logs sudo -H -u $owner chmod o+rw . sudo -H -u $owner chmod o+rw -R .testrepository if [ -f ".testrepository/0" ] ; then - .tox/dsvm-functional/bin/subunit-1to2 < .testrepository/0 > ./testrepository.subunit + .tox/$venv/bin/subunit-1to2 < .testrepository/0 > ./testrepository.subunit $SCRIPTS_DIR/subunit2html ./testrepository.subunit testr_results.html gzip -9 ./testrepository.subunit gzip -9 ./testr_results.html @@ -20,29 +22,29 @@ function generate_testr_results { fi } - function dsvm_functional_prep_func { : } +if [[ "$venv" == dsvm-functional* ]] || [[ "$venv" == dsvm-fullstack* ]] +then + owner=stack + sudo_env= + # Set owner permissions according to job's requirements. + cd $FWAAS_DIR + sudo chown -R $owner:stack $FWAAS_DIR + sudo chown -R $owner:stack $NEUTRON_DIR + # Prep the environment according to job's requirements. + $prep_func -owner=stack -prep_func="dsvm_functional_prep_func" + # Run tests + echo "Running neutron-fwaas $venv test suite" + set +e + sudo -H -u $owner $sudo_env tox -e $venv + testr_exit_code=$? + set -e -# Set owner permissions according to job's requirements. -cd $FWAAS_DIR -sudo chown -R $owner:stack $FWAAS_DIR -sudo chown -R $owner:stack $NEUTRON_DIR -# Prep the environment according to job's requirements. -$prep_func - -# Run tests -echo "Running neutron dsvm-functional test suite" -set +e -sudo -H -u $owner tox -e dsvm-functional -testr_exit_code=$? -set -e - -# Collect and parse results -generate_testr_results -exit $testr_exit_code + # Collect and parse results + generate_testr_results + exit $testr_exit_code +fi diff --git a/neutron_fwaas/tests/fullstack/README b/neutron_fwaas/tests/fullstack/README new file mode 100644 index 000000000..cf1b7111f --- /dev/null +++ b/neutron_fwaas/tests/fullstack/README @@ -0,0 +1 @@ +Please see neutron/TESTING.rst for more information about what Fullstack tests are. diff --git a/neutron_fwaas/tests/fullstack/__init__.py b/neutron_fwaas/tests/fullstack/__init__.py new file mode 100644 index 000000000..5c53d8822 --- /dev/null +++ b/neutron_fwaas/tests/fullstack/__init__.py @@ -0,0 +1,16 @@ +# 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 neutron.common import eventlet_utils + + +eventlet_utils.monkey_patch() diff --git a/neutron_fwaas/tests/fullstack/base.py b/neutron_fwaas/tests/fullstack/base.py new file mode 100644 index 000000000..6a64249db --- /dev/null +++ b/neutron_fwaas/tests/fullstack/base.py @@ -0,0 +1,67 @@ +# Copyright 2015 Red Hat, Inc. +# +# 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 os + +from oslo_config import cfg + +from neutron.tests import base as tests_base +from neutron.tests.fullstack.resources import client as client_resource +from neutron.tests import tools +from neutron.tests.unit import testlib_api + + +# This is the directory from which infra fetches log files for fullstack tests +DEFAULT_LOG_DIR = os.path.join('/opt/stack/logs/neutron-fwaas/', + 'dsvm-fullstack-logs') + + +class BaseFullStackTestCase(testlib_api.MySQLTestCaseMixin, + testlib_api.SqlTestCase): + """Base test class for full-stack tests.""" + + BUILD_WITH_MIGRATIONS = True + + def setUp(self, environment): + super(BaseFullStackTestCase, self).setUp() + + tests_base.setup_test_logging( + cfg.CONF, DEFAULT_LOG_DIR, '%s.txt' % self.get_name()) + + # NOTE(zzzeek): the opportunistic DB fixtures have built for + # us a per-test (or per-process) database. Set the URL of this + # database in CONF as the full stack tests need to actually run a + # neutron server against this database. + _orig_db_url = cfg.CONF.database.connection + cfg.CONF.set_override( + 'connection', str(self.engine.url), group='database') + self.addCleanup( + cfg.CONF.set_override, + "connection", _orig_db_url, group="database" + ) + + # NOTE(ihrachys): seed should be reset before environment fixture below + # since the latter starts services that may rely on generated port + # numbers + tools.reset_random_seed() + self.environment = environment + self.environment.test_name = self.get_name() + self.useFixture(self.environment) + self.client = self.environment.neutron_server.client + self.safe_client = self.useFixture( + client_resource.ClientFixture(self.client)) + + def get_name(self): + class_name, test_name = self.id().split(".")[-2:] + return "%s.%s" % (class_name, test_name) diff --git a/neutron_fwaas/tests/fullstack/resources/__init__.py b/neutron_fwaas/tests/fullstack/resources/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/neutron_fwaas/tests/fullstack/resources/client.py b/neutron_fwaas/tests/fullstack/resources/client.py new file mode 100644 index 000000000..d97675fd0 --- /dev/null +++ b/neutron_fwaas/tests/fullstack/resources/client.py @@ -0,0 +1,247 @@ +# Copyright (c) 2015 Thales Services SAS +# +# 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 netaddr + +import fixtures +from neutron_lib import constants +from neutronclient.common import exceptions + +from neutron.common import utils +from neutron.extensions import portbindings + + +def _safe_method(f): + @functools.wraps(f) + def delete(*args, **kwargs): + try: + return f(*args, **kwargs) + except exceptions.NotFound: + pass + return delete + + +class ClientFixture(fixtures.Fixture): + """Manage and cleanup neutron resources.""" + + def __init__(self, client): + super(ClientFixture, self).__init__() + self.client = client + + def _create_resource(self, resource_type, spec): + create = getattr(self.client, 'create_%s' % resource_type) + delete = getattr(self.client, 'delete_%s' % resource_type) + + body = {resource_type: spec} + resp = create(body=body) + data = resp[resource_type] + self.addCleanup(_safe_method(delete), data['id']) + return data + + def create_router(self, tenant_id, name=None, ha=False, + external_network=None): + resource_type = 'router' + + name = name or utils.get_rand_name(prefix=resource_type) + spec = {'tenant_id': tenant_id, 'name': name, 'ha': ha} + if external_network: + spec['external_gateway_info'] = {"network_id": external_network} + + return self._create_resource(resource_type, spec) + + def create_network(self, tenant_id, name=None, external=False): + resource_type = 'network' + + name = name or utils.get_rand_name(prefix=resource_type) + spec = {'tenant_id': tenant_id, 'name': name} + spec['router:external'] = external + return self._create_resource(resource_type, spec) + + def create_subnet(self, tenant_id, network_id, + cidr, gateway_ip=None, name=None, enable_dhcp=True, + ipv6_address_mode='slaac', ipv6_ra_mode='slaac'): + resource_type = 'subnet' + + name = name or utils.get_rand_name(prefix=resource_type) + ip_version = netaddr.IPNetwork(cidr).version + spec = {'tenant_id': tenant_id, 'network_id': network_id, 'name': name, + 'cidr': cidr, 'enable_dhcp': enable_dhcp, + 'ip_version': ip_version} + if ip_version == constants.IP_VERSION_6: + spec['ipv6_address_mode'] = ipv6_address_mode + spec['ipv6_ra_mode'] = ipv6_ra_mode + + if gateway_ip: + spec['gateway_ip'] = gateway_ip + + return self._create_resource(resource_type, spec) + + def create_port(self, tenant_id, network_id, hostname=None, + qos_policy_id=None, **kwargs): + spec = { + 'network_id': network_id, + 'tenant_id': tenant_id, + } + spec.update(kwargs) + if hostname is not None: + spec[portbindings.HOST_ID] = hostname + if qos_policy_id: + spec['qos_policy_id'] = qos_policy_id + return self._create_resource('port', spec) + + def create_floatingip(self, tenant_id, floating_network_id, + fixed_ip_address, port_id): + spec = { + 'floating_network_id': floating_network_id, + 'tenant_id': tenant_id, + 'fixed_ip_address': fixed_ip_address, + 'port_id': port_id + } + + return self._create_resource('floatingip', spec) + + def add_router_interface(self, router_id, subnet_id): + body = {'subnet_id': subnet_id} + router_interface_info = self.client.add_interface_router( + router=router_id, body=body) + self.addCleanup(_safe_method(self.client.remove_interface_router), + router=router_id, body=body) + return router_interface_info + + def create_qos_policy(self, tenant_id, name, description, shared): + policy = self.client.create_qos_policy( + body={'policy': {'name': name, + 'description': description, + 'shared': shared, + 'tenant_id': tenant_id}}) + + def detach_and_delete_policy(): + qos_policy_id = policy['policy']['id'] + ports_with_policy = self.client.list_ports( + qos_policy_id=qos_policy_id)['ports'] + for port in ports_with_policy: + self.client.update_port( + port['id'], + body={'port': {'qos_policy_id': None}}) + self.client.delete_qos_policy(qos_policy_id) + + # NOTE: We'll need to add support for detaching from network once + # create_network() supports qos_policy_id. + self.addCleanup(_safe_method(detach_and_delete_policy)) + + return policy['policy'] + + def create_bandwidth_limit_rule(self, tenant_id, qos_policy_id, limit=None, + burst=None): + rule = {'tenant_id': tenant_id} + if limit: + rule['max_kbps'] = limit + if burst: + rule['max_burst_kbps'] = burst + rule = self.client.create_bandwidth_limit_rule( + policy=qos_policy_id, + body={'bandwidth_limit_rule': rule}) + + self.addCleanup(_safe_method(self.client.delete_bandwidth_limit_rule), + rule['bandwidth_limit_rule']['id'], + qos_policy_id) + + return rule['bandwidth_limit_rule'] + + def create_dscp_marking_rule(self, tenant_id, qos_policy_id, dscp_mark=0): + rule = {'tenant_id': tenant_id} + if dscp_mark: + rule['dscp_mark'] = dscp_mark + rule = self.client.create_dscp_marking_rule( + policy=qos_policy_id, + body={'dscp_marking_rule': rule}) + + self.addCleanup(_safe_method(self.client.delete_dscp_marking_rule), + rule['dscp_marking_rule']['id'], + qos_policy_id) + + return rule['dscp_marking_rule'] + + def create_trunk(self, tenant_id, port_id, name=None, + admin_state_up=None, sub_ports=None): + """Create a trunk via API. + + :param tenant_id: ID of the tenant. + :param port_id: Parent port of trunk. + :param name: Name of the trunk. + :param admin_state_up: Admin state of the trunk. + :param sub_ports: List of subport dictionaries in format + {'port_id': , + 'segmentation_type': 'vlan', + 'segmentation_id': } + + :return: Dictionary with trunk's data returned from Neutron API. + """ + spec = { + 'port_id': port_id, + 'tenant_id': tenant_id, + } + if name is not None: + spec['name'] = name + if sub_ports is not None: + spec['sub_ports'] = sub_ports + if admin_state_up is not None: + spec['admin_state_up'] = admin_state_up + + trunk = self.client.create_trunk({'trunk': spec})['trunk'] + + if sub_ports: + self.addCleanup( + _safe_method(self.trunk_remove_subports), + tenant_id, trunk['id'], trunk['sub_ports']) + self.addCleanup(_safe_method(self.client.delete_trunk), trunk['id']) + + return trunk + + def trunk_add_subports(self, tenant_id, trunk_id, sub_ports): + """Add subports to the trunk. + + :param tenant_id: ID of the tenant. + :param trunk_id: ID of the trunk. + :param sub_ports: List of subport dictionaries to be added in format + {'port_id': , + 'segmentation_type': 'vlan', + 'segmentation_id': } + """ + spec = { + 'tenant_id': tenant_id, + 'sub_ports': sub_ports, + } + trunk = self.client.trunk_add_subports(trunk_id, spec) + + sub_ports_to_remove = [ + sub_port for sub_port in trunk['sub_ports'] + if sub_port in sub_ports] + self.addCleanup( + _safe_method(self.trunk_remove_subports), tenant_id, trunk_id, + sub_ports_to_remove) + + def trunk_remove_subports(self, tenant_id, trunk_id, sub_ports): + """Remove subports from the trunk. + + :param trunk_id: ID of the trunk. + :param sub_ports: List of subport port IDs. + """ + spec = { + 'tenant_id': tenant_id, + 'sub_ports': sub_ports, + } + return self.client.trunk_remove_subports(trunk_id, spec) diff --git a/neutron_fwaas/tests/fullstack/resources/config.py b/neutron_fwaas/tests/fullstack/resources/config.py new file mode 100644 index 000000000..778e4a528 --- /dev/null +++ b/neutron_fwaas/tests/fullstack/resources/config.py @@ -0,0 +1,294 @@ +# Copyright 2015 Red Hat, Inc. +# +# 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 tempfile + +import fixtures +from neutron_lib import constants + +from neutron.common import utils +from neutron.plugins.ml2.extensions import qos as qos_ext +from neutron.tests.common import config_fixtures +from neutron.tests.common.exclusive_resources import port +from neutron.tests.common import helpers as c_helpers + + +class ConfigFixture(fixtures.Fixture): + """A fixture that holds an actual Neutron configuration. + + Note that 'self.config' is intended to only be updated once, during + the constructor, so if this fixture is re-used (setUp is called twice), + then the dynamic configuration values won't change. The correct usage + is initializing a new instance of the class. + """ + def __init__(self, env_desc, host_desc, temp_dir, base_filename): + super(ConfigFixture, self).__init__() + self.config = config_fixtures.ConfigDict() + self.env_desc = env_desc + self.host_desc = host_desc + self.temp_dir = temp_dir + self.base_filename = base_filename + + def _setUp(self): + cfg_fixture = config_fixtures.ConfigFileFixture( + self.base_filename, self.config, self.temp_dir) + self.useFixture(cfg_fixture) + self.filename = cfg_fixture.filename + + +class NeutronConfigFixture(ConfigFixture): + + def __init__(self, env_desc, host_desc, temp_dir, + connection, rabbitmq_environment): + super(NeutronConfigFixture, self).__init__( + env_desc, host_desc, temp_dir, base_filename='neutron.conf') + + service_plugins = ['router', 'trunk'] + if env_desc.qos: + service_plugins.append('qos') + + self.config.update({ + 'DEFAULT': { + 'host': self._generate_host(), + 'state_path': self._generate_state_path(self.temp_dir), + 'api_paste_config': self._generate_api_paste(), + 'core_plugin': 'ml2', + 'service_plugins': ','.join(service_plugins), + 'auth_strategy': 'noauth', + 'debug': 'True', + 'transport_url': + 'rabbit://%(user)s:%(password)s@%(host)s:5672/%(vhost)s' % + {'user': rabbitmq_environment.user, + 'password': rabbitmq_environment.password, + 'host': rabbitmq_environment.host, + 'vhost': rabbitmq_environment.vhost}, + }, + 'database': { + 'connection': connection, + }, + 'oslo_concurrency': { + 'lock_path': '$state_path/lock', + }, + 'oslo_policy': { + 'policy_file': self._generate_policy_json(), + }, + }) + + def _setUp(self): + self.config['DEFAULT'].update({ + 'bind_port': self.useFixture( + port.ExclusivePort(constants.PROTO_NAME_TCP)).port + }) + super(NeutronConfigFixture, self)._setUp() + + def _generate_host(self): + return utils.get_rand_name(prefix='host-') + + def _generate_state_path(self, temp_dir): + # Assume that temp_dir will be removed by the caller + self.state_path = tempfile.mkdtemp(prefix='state_path', dir=temp_dir) + return self.state_path + + def _generate_api_paste(self): + return c_helpers.find_sample_file('api-paste.ini') + + def _generate_policy_json(self): + return c_helpers.find_sample_file('policy.json') + + +class ML2ConfigFixture(ConfigFixture): + + def __init__(self, env_desc, host_desc, temp_dir, tenant_network_types): + super(ML2ConfigFixture, self).__init__( + env_desc, host_desc, temp_dir, base_filename='ml2_conf.ini') + + mechanism_drivers = self.env_desc.mech_drivers + if self.env_desc.l2_pop: + mechanism_drivers += ',l2population' + + self.config.update({ + 'ml2': { + 'tenant_network_types': tenant_network_types, + 'mechanism_drivers': mechanism_drivers, + }, + 'ml2_type_vlan': { + 'network_vlan_ranges': 'physnet1:1000:2999', + }, + 'ml2_type_gre': { + 'tunnel_id_ranges': '1:1000', + }, + 'ml2_type_vxlan': { + 'vni_ranges': '1001:2000', + }, + }) + + if env_desc.qos: + self.config['ml2']['extension_drivers'] =\ + qos_ext.QOS_EXT_DRIVER_ALIAS + + +class OVSConfigFixture(ConfigFixture): + + def __init__(self, env_desc, host_desc, temp_dir, local_ip): + super(OVSConfigFixture, self).__init__( + env_desc, host_desc, temp_dir, + base_filename='openvswitch_agent.ini') + + self.tunneling_enabled = self.env_desc.tunneling_enabled + self.config.update({ + 'ovs': { + 'local_ip': local_ip, + 'integration_bridge': self._generate_integration_bridge(), + 'of_interface': host_desc.of_interface, + 'ovsdb_interface': host_desc.ovsdb_interface, + }, + 'securitygroup': { + 'firewall_driver': 'noop', + }, + 'agent': { + 'l2_population': str(self.env_desc.l2_pop), + 'arp_responder': str(self.env_desc.arp_responder), + } + }) + + if self.tunneling_enabled: + self.config['agent'].update({ + 'tunnel_types': self.env_desc.network_type}) + self.config['ovs'].update({ + 'tunnel_bridge': self._generate_tunnel_bridge(), + 'int_peer_patch_port': self._generate_int_peer(), + 'tun_peer_patch_port': self._generate_tun_peer()}) + else: + self.config['ovs']['bridge_mappings'] = ( + self._generate_bridge_mappings()) + + if env_desc.qos: + self.config['agent']['extensions'] = 'qos' + + def _setUp(self): + if self.config['ovs']['of_interface'] == 'native': + self.config['ovs'].update({ + 'of_listen_port': self.useFixture( + port.ExclusivePort(constants.PROTO_NAME_TCP)).port + }) + super(OVSConfigFixture, self)._setUp() + + def _generate_bridge_mappings(self): + return 'physnet1:%s' % utils.get_rand_device_name(prefix='br-eth') + + def _generate_integration_bridge(self): + return utils.get_rand_device_name(prefix='br-int') + + def _generate_tunnel_bridge(self): + return utils.get_rand_device_name(prefix='br-tun') + + def _generate_int_peer(self): + return utils.get_rand_device_name(prefix='patch-tun') + + def _generate_tun_peer(self): + return utils.get_rand_device_name(prefix='patch-int') + + def get_br_int_name(self): + return self.config.ovs.integration_bridge + + def get_br_phys_name(self): + return self.config.ovs.bridge_mappings.split(':')[1] + + def get_br_tun_name(self): + return self.config.ovs.tunnel_bridge + + +class LinuxBridgeConfigFixture(ConfigFixture): + + def __init__(self, env_desc, host_desc, temp_dir, local_ip, + physical_device_name): + super(LinuxBridgeConfigFixture, self).__init__( + env_desc, host_desc, temp_dir, + base_filename="linuxbridge_agent.ini" + ) + self.config.update({ + 'VXLAN': { + 'enable_vxlan': str(self.env_desc.tunneling_enabled), + 'local_ip': local_ip, + 'l2_population': str(self.env_desc.l2_pop), + } + }) + if env_desc.qos: + self.config.update({ + 'AGENT': { + 'extensions': 'qos' + } + }) + if self.env_desc.tunneling_enabled: + self.config.update({ + 'LINUX_BRIDGE': { + 'bridge_mappings': self._generate_bridge_mappings( + physical_device_name + ) + } + }) + else: + self.config.update({ + 'LINUX_BRIDGE': { + 'physical_interface_mappings': + self._generate_bridge_mappings( + physical_device_name + ) + } + }) + + def _generate_bridge_mappings(self, device_name): + return 'physnet1:%s' % device_name + + +class L3ConfigFixture(ConfigFixture): + + def __init__(self, env_desc, host_desc, temp_dir, integration_bridge=None): + super(L3ConfigFixture, self).__init__( + env_desc, host_desc, temp_dir, base_filename='l3_agent.ini') + if host_desc.l2_agent_type == constants.AGENT_TYPE_OVS: + self._prepare_config_with_ovs_agent(integration_bridge) + elif host_desc.l2_agent_type == constants.AGENT_TYPE_LINUXBRIDGE: + self._prepare_config_with_linuxbridge_agent() + self.config['DEFAULT'].update({ + 'debug': 'True', + 'test_namespace_suffix': self._generate_namespace_suffix(), + }) + + def _prepare_config_with_ovs_agent(self, integration_bridge): + self.config.update({ + 'DEFAULT': { + 'interface_driver': ('neutron.agent.linux.interface.' + 'OVSInterfaceDriver'), + 'ovs_integration_bridge': integration_bridge, + 'external_network_bridge': self._generate_external_bridge(), + } + }) + + def _prepare_config_with_linuxbridge_agent(self): + self.config.update({ + 'DEFAULT': { + 'interface_driver': ('neutron.agent.linux.interface.' + 'BridgeInterfaceDriver'), + } + }) + + def _generate_external_bridge(self): + return utils.get_rand_device_name(prefix='br-ex') + + def get_external_bridge(self): + return self.config.DEFAULT.external_network_bridge + + def _generate_namespace_suffix(self): + return utils.get_rand_name(prefix='test') diff --git a/neutron_fwaas/tests/fullstack/resources/environment.py b/neutron_fwaas/tests/fullstack/resources/environment.py new file mode 100644 index 000000000..e207cc290 --- /dev/null +++ b/neutron_fwaas/tests/fullstack/resources/environment.py @@ -0,0 +1,366 @@ +# Copyright 2015 Red Hat, Inc. +# +# 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 fixtures +from neutron_lib import constants +from neutronclient.common import exceptions as nc_exc +from oslo_config import cfg + +from neutron.agent.linux import ip_lib +from neutron.common import utils as common_utils +from neutron.plugins.ml2.drivers.linuxbridge.agent import \ + linuxbridge_neutron_agent as lb_agent +from neutron.tests.common.exclusive_resources import ip_address +from neutron.tests.common.exclusive_resources import ip_network +from neutron.tests.common import net_helpers +from neutron.tests.fullstack.resources import config +from neutron.tests.fullstack.resources import process + + +class EnvironmentDescription(object): + """A set of characteristics of an environment setup. + + Does the setup, as a whole, support tunneling? How about l2pop? + """ + def __init__(self, network_type='vxlan', l2_pop=True, qos=False, + mech_drivers='openvswitch,linuxbridge', arp_responder=False): + self.network_type = network_type + self.l2_pop = l2_pop + self.qos = qos + self.network_range = None + self.mech_drivers = mech_drivers + self.arp_responder = arp_responder + + @property + def tunneling_enabled(self): + return self.network_type in ('vxlan', 'gre') + + +class HostDescription(object): + """A set of characteristics of an environment Host. + + What agents should the host spawn? What mode should each agent operate + under? + """ + def __init__(self, l3_agent=False, of_interface='ovs-ofctl', + ovsdb_interface='vsctl', + l2_agent_type=constants.AGENT_TYPE_OVS): + self.l2_agent_type = l2_agent_type + self.l3_agent = l3_agent + self.of_interface = of_interface + self.ovsdb_interface = ovsdb_interface + + +class Host(fixtures.Fixture): + """The Host class models a physical host running agents, all reporting with + the same hostname. + + OpenStack installers or administrators connect compute nodes to the + physical tenant network by connecting the provider bridges to their + respective physical NICs. Or, if using tunneling, by configuring an + IP address on the appropriate physical NIC. The Host class does the same + with the connect_* methods. + + TODO(amuller): Add start/stop/restart methods that will start/stop/restart + all of the agents on this host. Add a kill method that stops all agents + and disconnects the host from other hosts. + """ + + def __init__(self, env_desc, host_desc, + test_name, neutron_config, + central_data_bridge, central_external_bridge): + self.env_desc = env_desc + self.host_desc = host_desc + self.test_name = test_name + self.neutron_config = neutron_config + self.central_data_bridge = central_data_bridge + self.central_external_bridge = central_external_bridge + self.host_namespace = None + self.agents = {} + # we need to cache already created "per network" bridges if linuxbridge + # agent is used on host: + self.network_bridges = {} + + def _setUp(self): + self.local_ip = self.allocate_local_ip() + + if self.host_desc.l2_agent_type == constants.AGENT_TYPE_OVS: + self.setup_host_with_ovs_agent() + elif self.host_desc.l2_agent_type == constants.AGENT_TYPE_LINUXBRIDGE: + self.setup_host_with_linuxbridge_agent() + if self.host_desc.l3_agent: + self.l3_agent = self.useFixture( + process.L3AgentFixture( + self.env_desc, self.host_desc, + self.test_name, + self.neutron_config, + self.l3_agent_cfg_fixture)) + + def setup_host_with_ovs_agent(self): + agent_cfg_fixture = config.OVSConfigFixture( + self.env_desc, self.host_desc, self.neutron_config.temp_dir, + self.local_ip) + self.useFixture(agent_cfg_fixture) + + if self.env_desc.tunneling_enabled: + self.useFixture( + net_helpers.OVSBridgeFixture( + agent_cfg_fixture.get_br_tun_name())).bridge + self.connect_to_internal_network_via_tunneling() + else: + br_phys = self.useFixture( + net_helpers.OVSBridgeFixture( + agent_cfg_fixture.get_br_phys_name())).bridge + self.connect_to_internal_network_via_vlans(br_phys) + + self.ovs_agent = self.useFixture( + process.OVSAgentFixture( + self.env_desc, self.host_desc, + self.test_name, self.neutron_config, agent_cfg_fixture)) + + if self.host_desc.l3_agent: + self.l3_agent_cfg_fixture = self.useFixture( + config.L3ConfigFixture( + self.env_desc, self.host_desc, + self.neutron_config.temp_dir, + self.ovs_agent.agent_cfg_fixture.get_br_int_name())) + br_ex = self.useFixture( + net_helpers.OVSBridgeFixture( + self.l3_agent_cfg_fixture.get_external_bridge())).bridge + self.connect_to_external_network(br_ex) + + def setup_host_with_linuxbridge_agent(self): + #First we need to provide connectivity for agent to prepare proper + #bridge mappings in agent's config: + self.host_namespace = self.useFixture( + net_helpers.NamespaceFixture(prefix="host-") + ).name + + self.connect_namespace_to_control_network() + + agent_cfg_fixture = config.LinuxBridgeConfigFixture( + self.env_desc, self.host_desc, + self.neutron_config.temp_dir, + self.local_ip, + physical_device_name=self.host_port.name + ) + self.useFixture(agent_cfg_fixture) + + self.linuxbridge_agent = self.useFixture( + process.LinuxBridgeAgentFixture( + self.env_desc, self.host_desc, + self.test_name, self.neutron_config, agent_cfg_fixture, + namespace=self.host_namespace + ) + ) + + if self.host_desc.l3_agent: + self.l3_agent_cfg_fixture = self.useFixture( + config.L3ConfigFixture( + self.env_desc, self.host_desc, + self.neutron_config.temp_dir)) + + def _connect_ovs_port(self, cidr_address): + ovs_device = self.useFixture( + net_helpers.OVSPortFixture( + bridge=self.central_data_bridge, + namespace=self.host_namespace)).port + # NOTE: This sets an IP address on the host's root namespace + # which is cleaned up when the device is deleted. + ovs_device.addr.add(cidr_address) + return ovs_device + + def connect_namespace_to_control_network(self): + self.host_port = self._connect_ovs_port( + common_utils.ip_to_cidr(self.local_ip, 24) + ) + self.host_port.link.set_up() + + def connect_to_internal_network_via_tunneling(self): + veth_1, veth_2 = self.useFixture( + net_helpers.VethFixture()).ports + + # NOTE: This sets an IP address on the host's root namespace + # which is cleaned up when the device is deleted. + veth_1.addr.add(common_utils.ip_to_cidr(self.local_ip, 32)) + + veth_1.link.set_up() + veth_2.link.set_up() + + def connect_to_internal_network_via_vlans(self, host_data_bridge): + # If using VLANs as a segmentation device, it's needed to connect + # a provider bridge to a centralized, shared bridge. + net_helpers.create_patch_ports( + self.central_data_bridge, host_data_bridge) + + def connect_to_external_network(self, host_external_bridge): + net_helpers.create_patch_ports( + self.central_external_bridge, host_external_bridge) + + def allocate_local_ip(self): + if not self.env_desc.network_range: + return str(self.useFixture( + ip_address.ExclusiveIPAddress( + '240.0.0.1', '240.255.255.254')).address) + return str(self.useFixture( + ip_address.ExclusiveIPAddress( + str(self.env_desc.network_range[2]), + str(self.env_desc.network_range[-2]))).address) + + def get_bridge(self, network_id): + if "ovs" in self.agents.keys(): + return self.ovs_agent.br_int + elif "linuxbridge" in self.agents.keys(): + bridge = self.network_bridges.get(network_id, None) + if not bridge: + br_prefix = lb_agent.LinuxBridgeManager.get_bridge_name( + network_id) + bridge = self.useFixture( + net_helpers.LinuxBridgeFixture( + prefix=br_prefix, + namespace=self.host_namespace, + prefix_is_full_name=True)).bridge + self.network_bridges[network_id] = bridge + return bridge + + @property + def hostname(self): + return self.neutron_config.config.DEFAULT.host + + @property + def l3_agent(self): + return self.agents['l3'] + + @l3_agent.setter + def l3_agent(self, agent): + self.agents['l3'] = agent + + @property + def ovs_agent(self): + return self.agents['ovs'] + + @ovs_agent.setter + def ovs_agent(self, agent): + self.agents['ovs'] = agent + + @property + def linuxbridge_agent(self): + return self.agents['linuxbridge'] + + @linuxbridge_agent.setter + def linuxbridge_agent(self, agent): + self.agents['linuxbridge'] = agent + + +class Environment(fixtures.Fixture): + """Represents a deployment topology. + + Environment is a collection of hosts. It starts a Neutron server + and a parametrized number of Hosts, each a collection of agents. + The Environment accepts a collection of HostDescription, each describing + the type of Host to create. + """ + + def __init__(self, env_desc, hosts_desc): + """ + :param env_desc: An EnvironmentDescription instance. + :param hosts_desc: A list of HostDescription instances. + """ + + super(Environment, self).__init__() + self.env_desc = env_desc + self.hosts_desc = hosts_desc + self.hosts = [] + + def wait_until_env_is_up(self): + common_utils.wait_until_true(self._processes_are_ready) + + def _processes_are_ready(self): + try: + running_agents = self.neutron_server.client.list_agents()['agents'] + agents_count = sum(len(host.agents) for host in self.hosts) + return len(running_agents) == agents_count + except nc_exc.NeutronClientException: + return False + + def _create_host(self, host_desc): + temp_dir = self.useFixture(fixtures.TempDir()).path + neutron_config = config.NeutronConfigFixture( + self.env_desc, host_desc, temp_dir, + cfg.CONF.database.connection, self.rabbitmq_environment) + self.useFixture(neutron_config) + + return self.useFixture( + Host(self.env_desc, + host_desc, + self.test_name, + neutron_config, + self.central_data_bridge, + self.central_external_bridge)) + + def _setUp(self): + self.temp_dir = self.useFixture(fixtures.TempDir()).path + + #we need this bridge before rabbit and neutron service will start + self.central_data_bridge = self.useFixture( + net_helpers.OVSBridgeFixture('cnt-data')).bridge + self.central_external_bridge = self.useFixture( + net_helpers.OVSBridgeFixture('cnt-ex')).bridge + + #Get rabbitmq address (and cnt-data network) + rabbitmq_ip_address = self._configure_port_for_rabbitmq() + self.rabbitmq_environment = self.useFixture( + process.RabbitmqEnvironmentFixture(host=rabbitmq_ip_address) + ) + + plugin_cfg_fixture = self.useFixture( + config.ML2ConfigFixture( + self.env_desc, self.hosts_desc, self.temp_dir, + self.env_desc.network_type)) + neutron_cfg_fixture = self.useFixture( + config.NeutronConfigFixture( + self.env_desc, None, self.temp_dir, + cfg.CONF.database.connection, self.rabbitmq_environment)) + self.neutron_server = self.useFixture( + process.NeutronServerFixture( + self.env_desc, None, + self.test_name, neutron_cfg_fixture, plugin_cfg_fixture)) + + self.hosts = [self._create_host(desc) for desc in self.hosts_desc] + + self.wait_until_env_is_up() + + def _configure_port_for_rabbitmq(self): + self.env_desc.network_range = self._get_network_range() + if not self.env_desc.network_range: + return "127.0.0.1" + rabbitmq_ip = str(self.env_desc.network_range[1]) + rabbitmq_port = ip_lib.IPDevice(self.central_data_bridge.br_name) + rabbitmq_port.addr.add(common_utils.ip_to_cidr(rabbitmq_ip, 24)) + rabbitmq_port.link.set_up() + + return rabbitmq_ip + + def _get_network_range(self): + #NOTE(slaweq): We need to choose IP address on which rabbitmq will be + # available because LinuxBridge agents are spawned in their own + # namespaces and need to know where the rabbitmq server is listening. + # For ovs agent it is not necessary because agents are spawned in + # globalscope together with rabbitmq server so default localhost + # address is fine for them + for desc in self.hosts_desc: + if desc.l2_agent_type == constants.AGENT_TYPE_LINUXBRIDGE: + return self.useFixture( + ip_network.ExclusiveIPNetwork( + "240.0.0.0", "240.255.255.255", "24")).network diff --git a/neutron_fwaas/tests/fullstack/resources/machine.py b/neutron_fwaas/tests/fullstack/resources/machine.py new file mode 100644 index 000000000..e9eaecc1e --- /dev/null +++ b/neutron_fwaas/tests/fullstack/resources/machine.py @@ -0,0 +1,168 @@ +# Copyright 2015 Red Hat, Inc. +# +# 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 itertools + +import netaddr + +from neutron_lib import constants + +from neutron.agent.linux import ip_lib +from neutron.common import utils +from neutron.extensions import portbindings as pbs +from neutron.tests.common import machine_fixtures +from neutron.tests.common import net_helpers + + +class FakeFullstackMachinesList(list): + """A list of items implementing the FakeFullstackMachine interface.""" + + def block_until_all_boot(self): + for vm in self: + vm.block_until_boot() + + def ping_all(self): + # Generate an iterable of all unique pairs. For example: + # itertools.combinations(range(3), 2) results in: + # ((0, 1), (0, 2), (1, 2)) + for vm_1, vm_2 in itertools.combinations(self, 2): + vm_1.block_until_ping(vm_2.ip) + + +class FakeFullstackMachine(machine_fixtures.FakeMachineBase): + + def __init__(self, host, network_id, tenant_id, safe_client, + neutron_port=None, bridge_name=None): + super(FakeFullstackMachine, self).__init__() + self.host = host + self.tenant_id = tenant_id + self.network_id = network_id + self.safe_client = safe_client + self.neutron_port = neutron_port + self.bridge_name = bridge_name + + def _setUp(self): + super(FakeFullstackMachine, self)._setUp() + + self.bridge = self._get_bridge() + + if not self.neutron_port: + self.neutron_port = self.safe_client.create_port( + network_id=self.network_id, + tenant_id=self.tenant_id, + hostname=self.host.hostname) + mac_address = self.neutron_port['mac_address'] + hybrid_plug = self.neutron_port[pbs.VIF_DETAILS].get( + pbs.OVS_HYBRID_PLUG, False) + + self.bind_port_if_needed() + self.port = self.useFixture( + net_helpers.PortFixture.get( + self.bridge, self.namespace, mac_address, + self.neutron_port['id'], hybrid_plug)).port + + for fixed_ip in self.neutron_port['fixed_ips']: + self._configure_ipaddress(fixed_ip) + + def bind_port_if_needed(self): + if self.neutron_port[pbs.VIF_TYPE] == pbs.VIF_TYPE_UNBOUND: + self.safe_client.client.update_port( + self.neutron_port['id'], + {'port': {pbs.HOST_ID: self.host.hostname}}) + self.addCleanup(self.safe_client.client.update_port, + self.neutron_port['id'], + {'port': {pbs.HOST_ID: ''}}) + + def _get_bridge(self): + if self.bridge_name is None: + return self.host.get_bridge(self.network_id) + agent_type = self.host.host_desc.l2_agent_type + if agent_type == constants.AGENT_TYPE_OVS: + new_bridge = self.useFixture( + net_helpers.OVSTrunkBridgeFixture(self.bridge_name)).bridge + else: + raise NotImplementedError( + "Support for %s agent is not implemented." % agent_type) + + return new_bridge + + def _configure_ipaddress(self, fixed_ip): + if (netaddr.IPAddress(fixed_ip['ip_address']).version == + constants.IP_VERSION_6): + # v6Address/default_route is auto-configured. + self._ipv6 = fixed_ip['ip_address'] + else: + self._ip = fixed_ip['ip_address'] + subnet_id = fixed_ip['subnet_id'] + subnet = self.safe_client.client.show_subnet(subnet_id) + prefixlen = netaddr.IPNetwork(subnet['subnet']['cidr']).prefixlen + self._ip_cidr = '%s/%s' % (self._ip, prefixlen) + + # TODO(amuller): Support DHCP + self.port.addr.add(self.ip_cidr) + + self.gateway_ip = subnet['subnet']['gateway_ip'] + if self.gateway_ip: + net_helpers.set_namespace_gateway(self.port, self.gateway_ip) + + @property + def ipv6(self): + return self._ipv6 + + @property + def ip(self): + return self._ip + + @property + def ip_cidr(self): + return self._ip_cidr + + def block_until_boot(self): + utils.wait_until_true( + lambda: (self.safe_client.client.show_port(self.neutron_port['id']) + ['port']['status'] == 'ACTIVE'), + sleep=3) + + def destroy(self): + """Destroy this fake machine. + + This should simulate deletion of a vm. It doesn't call cleanUp(). + """ + self.safe_client.client.update_port( + self.neutron_port['id'], + {'port': {pbs.HOST_ID: ''}} + ) + # All associated vlan interfaces are deleted too + self.bridge.delete_port(self.port.name) + + ip_wrap = ip_lib.IPWrapper(self.namespace) + ip_wrap.netns.delete(self.namespace) + + +class FakeFullstackTrunkMachine(FakeFullstackMachine): + def __init__(self, trunk, *args, **kwargs): + super(FakeFullstackTrunkMachine, self).__init__(*args, **kwargs) + self.trunk = trunk + + def add_vlan_interface(self, mac_address, ip_address, segmentation_id): + """Add VLAN interface to VM's namespace. + + :param mac_address: MAC address to be set on VLAN interface. + :param ip_address: The IPNetwork instance containing IP address + assigned to the interface. + :param segmentation_id: VLAN tag added to the interface. + """ + net_helpers.create_vlan_interface( + self.namespace, self.port.name, mac_address, ip_address, + segmentation_id) diff --git a/neutron_fwaas/tests/fullstack/resources/process.py b/neutron_fwaas/tests/fullstack/resources/process.py new file mode 100644 index 000000000..2d49653e0 --- /dev/null +++ b/neutron_fwaas/tests/fullstack/resources/process.py @@ -0,0 +1,235 @@ +# Copyright 2015 Red Hat, Inc. +# +# 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 datetime +from distutils import spawn +import os +import signal + +import fixtures +from neutronclient.common import exceptions as nc_exc +from neutronclient.v2_0 import client + +from neutron.agent.linux import async_process +from neutron.agent.linux import utils +from neutron.common import utils as common_utils +from neutron.tests import base +from neutron.tests.common import net_helpers +from neutron.tests.fullstack import base as fullstack_base + + +class ProcessFixture(fixtures.Fixture): + def __init__(self, test_name, process_name, exec_name, config_filenames, + namespace=None, kill_signal=signal.SIGKILL): + super(ProcessFixture, self).__init__() + self.test_name = test_name + self.process_name = process_name + self.exec_name = exec_name + self.config_filenames = config_filenames + self.process = None + self.kill_signal = kill_signal + self.namespace = namespace + + def _setUp(self): + self.start() + self.addCleanup(self.stop) + + def start(self): + test_name = base.sanitize_log_path(self.test_name) + + log_dir = os.path.join(fullstack_base.DEFAULT_LOG_DIR, test_name) + common_utils.ensure_dir(log_dir) + + timestamp = datetime.datetime.now().strftime("%Y-%m-%d--%H-%M-%S-%f") + log_file = "%s--%s.log" % (self.process_name, timestamp) + cmd = [spawn.find_executable(self.exec_name), + '--log-dir', log_dir, + '--log-file', log_file] + for filename in self.config_filenames: + cmd += ['--config-file', filename] + run_as_root = bool(self.namespace) + self.process = async_process.AsyncProcess( + cmd, run_as_root=run_as_root, namespace=self.namespace + ) + self.process.start(block=True) + + def stop(self): + self.process.stop(block=True, kill_signal=self.kill_signal) + + +class RabbitmqEnvironmentFixture(fixtures.Fixture): + + def __init__(self, host="127.0.0.1"): + super(RabbitmqEnvironmentFixture, self).__init__() + self.host = host + + def _setUp(self): + self.user = common_utils.get_rand_name(prefix='user') + self.password = common_utils.get_rand_name(prefix='pass') + self.vhost = common_utils.get_rand_name(prefix='vhost') + + self._execute('add_user', self.user, self.password) + self.addCleanup(self._execute, 'delete_user', self.user) + + self._execute('add_vhost', self.vhost) + self.addCleanup(self._execute, 'delete_vhost', self.vhost) + + self._execute('set_permissions', '-p', self.vhost, self.user, + '.*', '.*', '.*') + + def _execute(self, *args): + cmd = ['rabbitmqctl'] + cmd.extend(args) + utils.execute(cmd, run_as_root=True) + + +class NeutronServerFixture(fixtures.Fixture): + + NEUTRON_SERVER = "neutron-server" + + def __init__(self, env_desc, host_desc, + test_name, neutron_cfg_fixture, plugin_cfg_fixture): + super(NeutronServerFixture, self).__init__() + self.env_desc = env_desc + self.host_desc = host_desc + self.test_name = test_name + self.neutron_cfg_fixture = neutron_cfg_fixture + self.plugin_cfg_fixture = plugin_cfg_fixture + + def _setUp(self): + config_filenames = [self.neutron_cfg_fixture.filename, + self.plugin_cfg_fixture.filename] + + self.process_fixture = self.useFixture(ProcessFixture( + test_name=self.test_name, + process_name=self.NEUTRON_SERVER, + exec_name=self.NEUTRON_SERVER, + config_filenames=config_filenames, + kill_signal=signal.SIGTERM)) + + common_utils.wait_until_true(self.server_is_live) + + def server_is_live(self): + try: + self.client.list_networks() + return True + except nc_exc.NeutronClientException: + return False + + @property + def client(self): + url = ("http://127.0.0.1:%s" % + self.neutron_cfg_fixture.config.DEFAULT.bind_port) + return client.Client(auth_strategy="noauth", endpoint_url=url) + + +class OVSAgentFixture(fixtures.Fixture): + + NEUTRON_OVS_AGENT = "neutron-openvswitch-agent" + + def __init__(self, env_desc, host_desc, + test_name, neutron_cfg_fixture, agent_cfg_fixture): + super(OVSAgentFixture, self).__init__() + self.env_desc = env_desc + self.host_desc = host_desc + self.test_name = test_name + self.neutron_cfg_fixture = neutron_cfg_fixture + self.neutron_config = self.neutron_cfg_fixture.config + self.agent_cfg_fixture = agent_cfg_fixture + self.agent_config = agent_cfg_fixture.config + + def _setUp(self): + self.br_int = self.useFixture( + net_helpers.OVSBridgeFixture( + self.agent_cfg_fixture.get_br_int_name())).bridge + + config_filenames = [self.neutron_cfg_fixture.filename, + self.agent_cfg_fixture.filename] + + self.process_fixture = self.useFixture(ProcessFixture( + test_name=self.test_name, + process_name=self.NEUTRON_OVS_AGENT, + exec_name=spawn.find_executable( + 'ovs_agent.py', + path=os.path.join(base.ROOTDIR, 'common', 'agents')), + config_filenames=config_filenames, + kill_signal=signal.SIGTERM)) + + +class LinuxBridgeAgentFixture(fixtures.Fixture): + + NEUTRON_LINUXBRIDGE_AGENT = "neutron-linuxbridge-agent" + + def __init__(self, env_desc, host_desc, test_name, + neutron_cfg_fixture, agent_cfg_fixture, + namespace=None): + super(LinuxBridgeAgentFixture, self).__init__() + self.env_desc = env_desc + self.host_desc = host_desc + self.test_name = test_name + self.neutron_cfg_fixture = neutron_cfg_fixture + self.neutron_config = self.neutron_cfg_fixture.config + self.agent_cfg_fixture = agent_cfg_fixture + self.agent_config = agent_cfg_fixture.config + self.namespace = namespace + + def _setUp(self): + config_filenames = [self.neutron_cfg_fixture.filename, + self.agent_cfg_fixture.filename] + + self.process_fixture = self.useFixture( + ProcessFixture( + test_name=self.test_name, + process_name=self.NEUTRON_LINUXBRIDGE_AGENT, + exec_name=self.NEUTRON_LINUXBRIDGE_AGENT, + config_filenames=config_filenames, + namespace=self.namespace + ) + ) + + +class L3AgentFixture(fixtures.Fixture): + + NEUTRON_L3_AGENT = "neutron-l3-agent" + + def __init__(self, env_desc, host_desc, test_name, + neutron_cfg_fixture, l3_agent_cfg_fixture, + namespace=None): + super(L3AgentFixture, self).__init__() + self.env_desc = env_desc + self.host_desc = host_desc + self.test_name = test_name + self.neutron_cfg_fixture = neutron_cfg_fixture + self.l3_agent_cfg_fixture = l3_agent_cfg_fixture + self.namespace = namespace + + def _setUp(self): + self.plugin_config = self.l3_agent_cfg_fixture.config + + config_filenames = [self.neutron_cfg_fixture.filename, + self.l3_agent_cfg_fixture.filename] + self.process_fixture = self.useFixture( + ProcessFixture( + test_name=self.test_name, + process_name=self.NEUTRON_L3_AGENT, + exec_name=spawn.find_executable( + 'l3_agent.py', + path=os.path.join(base.ROOTDIR, 'common', 'agents')), + config_filenames=config_filenames, + namespace=self.namespace + ) + ) + + def get_namespace_suffix(self): + return self.plugin_config.DEFAULT.test_namespace_suffix diff --git a/neutron_fwaas/tests/fullstack/test_l3_agent.py b/neutron_fwaas/tests/fullstack/test_l3_agent.py new file mode 100644 index 000000000..58d1ea02c --- /dev/null +++ b/neutron_fwaas/tests/fullstack/test_l3_agent.py @@ -0,0 +1,183 @@ +# Copyright 2015 Red Hat, Inc. +# +# 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 netaddr + +from neutron_lib import constants +from oslo_utils import uuidutils + +from neutron.agent.l3 import agent as l3_agent +from neutron.agent.l3 import namespaces +from neutron.agent.linux import ip_lib +from neutron.common import utils as common_utils +from neutron.tests.common.exclusive_resources import ip_network +from neutron.tests.common import machine_fixtures +from neutron.tests.fullstack import base +from neutron.tests.fullstack.resources import environment +from neutron.tests.fullstack.resources import machine +from neutron.tests.unit import testlib_api + +load_tests = testlib_api.module_load_tests + + +class TestL3Agent(base.BaseFullStackTestCase): + + def _create_external_network_and_subnet(self, tenant_id): + network = self.safe_client.create_network( + tenant_id, name='public', external=True) + cidr = self.useFixture( + ip_network.ExclusiveIPNetwork( + "240.0.0.0", "240.255.255.255", "24")).network + subnet = self.safe_client.create_subnet( + tenant_id, network['id'], cidr, + enable_dhcp=False) + return network, subnet + + def block_until_port_status_active(self, port_id): + def is_port_status_active(): + port = self.client.show_port(port_id) + return port['port']['status'] == 'ACTIVE' + common_utils.wait_until_true(lambda: is_port_status_active(), sleep=1) + + def _create_net_subnet_and_vm(self, tenant_id, subnet_cidrs, host, router): + network = self.safe_client.create_network(tenant_id) + for cidr in subnet_cidrs: + # For IPv6 subnets, enable_dhcp should be set to true. + enable_dhcp = (netaddr.IPNetwork(cidr).version == + constants.IP_VERSION_6) + subnet = self.safe_client.create_subnet( + tenant_id, network['id'], cidr, enable_dhcp=enable_dhcp) + + router_interface_info = self.safe_client.add_router_interface( + router['id'], subnet['id']) + self.block_until_port_status_active( + router_interface_info['port_id']) + + vm = self.useFixture( + machine.FakeFullstackMachine( + host, network['id'], tenant_id, self.safe_client)) + vm.block_until_boot() + return vm + + +class TestLegacyL3Agent(TestL3Agent): + + def setUp(self): + host_descriptions = [ + environment.HostDescription(l3_agent=True), + environment.HostDescription()] + env = environment.Environment( + environment.EnvironmentDescription( + network_type='vlan', l2_pop=False), + host_descriptions) + super(TestLegacyL3Agent, self).setUp(env) + + def _get_namespace(self, router_id): + return namespaces.build_ns_name(l3_agent.NS_PREFIX, router_id) + + def _assert_namespace_exists(self, ns_name): + ip = ip_lib.IPWrapper(ns_name) + common_utils.wait_until_true(lambda: ip.netns.exists(ns_name)) + + def test_namespace_exists(self): + tenant_id = uuidutils.generate_uuid() + + router = self.safe_client.create_router(tenant_id) + network = self.safe_client.create_network(tenant_id) + subnet = self.safe_client.create_subnet( + tenant_id, network['id'], '20.0.0.0/24', gateway_ip='20.0.0.1') + self.safe_client.add_router_interface(router['id'], subnet['id']) + + namespace = "%s@%s" % ( + self._get_namespace(router['id']), + self.environment.hosts[0].l3_agent.get_namespace_suffix(), ) + self._assert_namespace_exists(namespace) + + def test_east_west_traffic(self): + tenant_id = uuidutils.generate_uuid() + router = self.safe_client.create_router(tenant_id) + + vm1 = self._create_net_subnet_and_vm( + tenant_id, ['20.0.0.0/24', '2001:db8:aaaa::/64'], + self.environment.hosts[0], router) + vm2 = self._create_net_subnet_and_vm( + tenant_id, ['21.0.0.0/24', '2001:db8:bbbb::/64'], + self.environment.hosts[1], router) + + vm1.block_until_ping(vm2.ip) + # Verify ping6 from vm2 to vm1 IPv6 Address + vm2.block_until_ping(vm1.ipv6) + + def test_snat_and_floatingip(self): + # This function creates external network and boots an extrenal vm + # on it with gateway ip and connected to central_external_bridge. + # Later it creates a tenant vm on tenant network, with tenant router + # connected to tenant network and external network. + # To test snat and floatingip, try ping between tenant and external vms + tenant_id = uuidutils.generate_uuid() + ext_net, ext_sub = self._create_external_network_and_subnet(tenant_id) + external_vm = self.useFixture( + machine_fixtures.FakeMachine( + self.environment.central_external_bridge, + common_utils.ip_to_cidr(ext_sub['gateway_ip'], 24))) + + router = self.safe_client.create_router(tenant_id, + external_network=ext_net['id']) + vm = self._create_net_subnet_and_vm( + tenant_id, ['20.0.0.0/24'], + self.environment.hosts[1], router) + + # ping external vm to test snat + vm.block_until_ping(external_vm.ip) + + fip = self.safe_client.create_floatingip( + tenant_id, ext_net['id'], vm.ip, vm.neutron_port['id']) + + # ping floating ip from external vm + external_vm.block_until_ping(fip['floating_ip_address']) + + +class TestHAL3Agent(base.BaseFullStackTestCase): + + def setUp(self): + host_descriptions = [ + environment.HostDescription(l3_agent=True) for _ in range(2)] + env = environment.Environment( + environment.EnvironmentDescription( + network_type='vxlan', l2_pop=True), + host_descriptions) + super(TestHAL3Agent, self).setUp(env) + + def _is_ha_router_active_on_one_agent(self, router_id): + agents = self.client.list_l3_agent_hosting_routers(router_id) + return ( + agents['agents'][0]['ha_state'] != agents['agents'][1]['ha_state']) + + def test_ha_router(self): + # TODO(amuller): Test external connectivity before and after a + # failover, see: https://review.openstack.org/#/c/196393/ + + tenant_id = uuidutils.generate_uuid() + router = self.safe_client.create_router(tenant_id, ha=True) + agents = self.client.list_l3_agent_hosting_routers(router['id']) + self.assertEqual(2, len(agents['agents']), + 'HA router must be scheduled to both nodes') + + common_utils.wait_until_true( + functools.partial( + self._is_ha_router_active_on_one_agent, + router['id']), + timeout=90) diff --git a/neutron_fwaas/tests/fullstack/utils.py b/neutron_fwaas/tests/fullstack/utils.py new file mode 100644 index 000000000..5a0f9623c --- /dev/null +++ b/neutron_fwaas/tests/fullstack/utils.py @@ -0,0 +1,24 @@ +# 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. + + +def get_ovs_interface_scenarios(): + return [ + ('openflow-cli_ovsdb-cli', {'of_interface': 'ovs-ofctl', + 'ovsdb_interface': 'vsctl'}), + ('openflow-native_ovsdb-cli', {'of_interface': 'native', + 'ovsdb_interface': 'vsctl'}), + ('openflow-cli_ovsdb-native', {'of_interface': 'ovs-ofctl', + 'ovsdb_interface': 'native'}), + ('openflow-native_ovsdb-native', {'of_interface': 'native', + 'ovsdb_interface': 'native'}), + ] diff --git a/playbooks/neutron-fwaas-fullstack/post.yaml b/playbooks/neutron-fwaas-fullstack/post.yaml new file mode 100644 index 000000000..dac875340 --- /dev/null +++ b/playbooks/neutron-fwaas-fullstack/post.yaml @@ -0,0 +1,80 @@ +- hosts: primary + tasks: + + - name: Copy files from {{ ansible_user_dir }}/workspace/ on node + synchronize: + src: '{{ ansible_user_dir }}/workspace/' + dest: '{{ zuul.executor.log_root }}' + mode: pull + copy_links: true + verify_host: true + rsync_opts: + - --include=**/*nose_results.html + - --include=*/ + - --exclude=* + - --prune-empty-dirs + + - name: Copy files from {{ ansible_user_dir }}/workspace/ on node + synchronize: + src: '{{ ansible_user_dir }}/workspace/' + dest: '{{ zuul.executor.log_root }}' + mode: pull + copy_links: true + verify_host: true + rsync_opts: + - --include=**/*testr_results.html.gz + - --include=*/ + - --exclude=* + - --prune-empty-dirs + + - name: Copy files from {{ ansible_user_dir }}/workspace/ on node + synchronize: + src: '{{ ansible_user_dir }}/workspace/' + dest: '{{ zuul.executor.log_root }}' + mode: pull + copy_links: true + verify_host: true + rsync_opts: + - --include=/.testrepository/tmp* + - --include=*/ + - --exclude=* + - --prune-empty-dirs + + - name: Copy files from {{ ansible_user_dir }}/workspace/ on node + synchronize: + src: '{{ ansible_user_dir }}/workspace/' + dest: '{{ zuul.executor.log_root }}' + mode: pull + copy_links: true + verify_host: true + rsync_opts: + - --include=**/*testrepository.subunit.gz + - --include=*/ + - --exclude=* + - --prune-empty-dirs + + - name: Copy files from {{ ansible_user_dir }}/workspace/ on node + synchronize: + src: '{{ ansible_user_dir }}/workspace/' + dest: '{{ zuul.executor.log_root }}/tox' + mode: pull + copy_links: true + verify_host: true + rsync_opts: + - --include=/.tox/*/log/* + - --include=*/ + - --exclude=* + - --prune-empty-dirs + + - name: Copy files from {{ ansible_user_dir }}/workspace/ on node + synchronize: + src: '{{ ansible_user_dir }}/workspace/' + dest: '{{ zuul.executor.log_root }}' + mode: pull + copy_links: true + verify_host: true + rsync_opts: + - --include=/logs/** + - --include=*/ + - --exclude=* + - --prune-empty-dirs diff --git a/playbooks/neutron-fwaas-fullstack/run.yaml b/playbooks/neutron-fwaas-fullstack/run.yaml new file mode 100644 index 000000000..57f60915b --- /dev/null +++ b/playbooks/neutron-fwaas-fullstack/run.yaml @@ -0,0 +1,54 @@ +- hosts: all + name: neutron-fwaas-fullstack + tasks: + + - name: Ensure legacy workspace directory + file: + path: '{{ ansible_user_dir }}/workspace' + state: directory + + - shell: + cmd: | + set -e + set -x + cat > clonemap.yaml << EOF + clonemap: + - name: openstack-infra/devstack-gate + dest: devstack-gate + EOF + /usr/zuul-env/bin/zuul-cloner -m clonemap.yaml --cache-dir /opt/git \ + git://git.openstack.org \ + openstack-infra/devstack-gate + executable: /bin/bash + chdir: '{{ ansible_user_dir }}/workspace' + environment: '{{ zuul | zuul_legacy_vars }}' + + - shell: + cmd: | + set -e + set -x + export PYTHONUNBUFFERED=true + export DEVSTACK_GATE_TEMPEST=0 + export DEVSTACK_GATE_EXERCISES=0 + export DEVSTACK_GATE_NEUTRON=1 + export DEVSTACK_GATE_INSTALL_TESTONLY=1 + export BRANCH_OVERRIDE=default + if [ "$BRANCH_OVERRIDE" != "default" ] ; then + export OVERRIDE_ZUUL_BRANCH=$BRANCH_OVERRIDE + fi + + function gate_hook { + bash -xe $BASE/new/neutron/neutron/tests/contrib/gate_hook.sh dsvm-fullstack + } + export -f gate_hook + + function post_test_hook { + bash -xe $BASE/new/neutron/neutron/tests/contrib/post_test_hook.sh dsvm-fullstack + } + export -f post_test_hook + + cp devstack-gate/devstack-vm-gate-wrap.sh ./safe-devstack-vm-gate-wrap.sh + ./safe-devstack-vm-gate-wrap.sh + executable: /bin/bash + chdir: '{{ ansible_user_dir }}/workspace' + environment: '{{ zuul | zuul_legacy_vars }}' diff --git a/tools/configure_for_func_testing.sh b/tools/configure_for_func_testing.sh new file mode 100755 index 000000000..84869292f --- /dev/null +++ b/tools/configure_for_func_testing.sh @@ -0,0 +1,281 @@ +#!/usr/bin/env bash + +# 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. + + +set -e + + +# Control variable used to determine whether to execute this script +# directly or allow the gate_hook to import. +IS_GATE=${IS_GATE:-False} +USE_CONSTRAINT_ENV=${USE_CONSTRAINT_ENV:-True} + + +if [[ "$IS_GATE" != "True" ]] && [[ "$#" -lt 1 ]]; then + >&2 echo "Usage: $0 /path/to/devstack [-i] +Configure a host to run Neutron's functional test suite. + +-i Install Neutron's package dependencies. By default, it is assumed + that devstack has already been used to deploy neutron-fwaas to the + target host and that package dependencies need not be installed. + +Warning: This script relies on devstack to perform extensive +modification to the underlying host. It is recommended that it be +invoked only on a throw-away VM." + exit 1 +fi + + +# Skip the first argument +OPTIND=2 +while getopts ":i" opt; do + case $opt in + i) + INSTALL_BASE_DEPENDENCIES=True + ;; + esac + +done + +# Default to environment variables to permit the gate_hook to override +# when sourcing. +VENV=${VENV:-dsvm-functional} +DEVSTACK_PATH=${DEVSTACK_PATH:-$1} +PROJECT_NAME=${PROJECT_NAME:-neutron-fwaas} +REPO_BASE=${GATE_DEST:-$(cd $(dirname "$0")/../.. && pwd)} +INSTALL_MYSQL_ONLY=${INSTALL_MYSQL_ONLY:-False} +# The gate should automatically install dependencies. +INSTALL_BASE_DEPENDENCIES=${INSTALL_BASE_DEPENDENCIES:-$IS_GATE} + + +if [ ! -f "$DEVSTACK_PATH/stack.sh" ]; then + >&2 echo "Unable to find devstack at '$DEVSTACK_PATH'. Please verify that the specified path points to a valid devstack repo." + exit 1 +fi + + +set -x + + +function _init { + # Subsequently-called devstack functions depend on the following variables. + HOST_IP=127.0.0.1 + FILES=$DEVSTACK_PATH/files + TOP_DIR=$DEVSTACK_PATH + + source $DEVSTACK_PATH/stackrc + + # Allow the gate to override values set by stackrc. + DEST=${GATE_DEST:-$DEST} + STACK_USER=${GATE_STACK_USER:-$STACK_USER} +} + + +function _install_base_deps { + echo_summary "Installing base dependencies" + + INSTALL_TESTONLY_PACKAGES=True + PACKAGES=$(get_packages general,neutron,q-agt,q-l3) + # Do not install 'python-' prefixed packages other than + # python-dev*. Neutron's functional testing relies on deployment + # to a tox env so there is no point in installing python + # dependencies system-wide. + PACKAGES=$(echo $PACKAGES | perl -pe 's|python-(?!dev)[^ ]*||g') + install_package $PACKAGES +} + + +function _install_rpc_backend { + echo_summary "Installing rabbitmq" + + RABBIT_USERID=${RABBIT_USERID:-stackrabbit} + RABBIT_HOST=${RABBIT_HOST:-$SERVICE_HOST} + RABBIT_PASSWORD=${RABBIT_HOST:-secretrabbit} + + source $DEVSTACK_PATH/lib/rpc_backend + + enable_service rabbit + install_rpc_backend + restart_rpc_backend +} + + +# _install_databases [install_pg] +function _install_databases { + local install_pg=${1:-True} + + echo_summary "Installing databases" + + # Avoid attempting to configure the db if it appears to already + # have run. The setup as currently defined is not idempotent. + if mysql openstack_citest > /dev/null 2>&1 < /dev/null; then + echo_summary "DB config appears to be complete, skipping." + return 0 + fi + + MYSQL_PASSWORD=${MYSQL_PASSWORD:-secretmysql} + DATABASE_PASSWORD=${DATABASE_PASSWORD:-secretdatabase} + + source $DEVSTACK_PATH/lib/database + + enable_service mysql + initialize_database_backends + install_database + configure_database_mysql + + if [[ "$install_pg" == "True" ]]; then + enable_service postgresql + initialize_database_backends + install_database + configure_database_postgresql + fi + + # Set up the 'openstack_citest' user and database in each backend + tmp_dir=$(mktemp -d) + trap "rm -rf $tmp_dir" EXIT + + cat << EOF > $tmp_dir/mysql.sql +CREATE DATABASE openstack_citest; +CREATE USER 'openstack_citest'@'localhost' IDENTIFIED BY 'openstack_citest'; +CREATE USER 'openstack_citest' IDENTIFIED BY 'openstack_citest'; +GRANT ALL PRIVILEGES ON *.* TO 'openstack_citest'@'localhost'; +GRANT ALL PRIVILEGES ON *.* TO 'openstack_citest'; +FLUSH PRIVILEGES; +EOF + /usr/bin/mysql -u root < $tmp_dir/mysql.sql + + if [[ "$install_pg" == "True" ]]; then + cat << EOF > $tmp_dir/postgresql.sql +CREATE USER openstack_citest WITH CREATEDB LOGIN PASSWORD 'openstack_citest'; +CREATE DATABASE openstack_citest WITH OWNER openstack_citest; +EOF + + # User/group postgres needs to be given access to tmp_dir + setfacl -m g:postgres:rwx $tmp_dir + sudo -u postgres /usr/bin/psql --file=$tmp_dir/postgresql.sql + fi +} + + +function _install_agent_deps { + echo_summary "Installing agent dependencies" + + ENABLED_SERVICES=q-agt,q-dhcp,q-l3 + install_neutron_agent_packages +} + + +# Set up the rootwrap sudoers for neutron to target the rootwrap +# configuration deployed in the venv. +function _install_rootwrap_sudoers { + echo_summary "Installing rootwrap sudoers file" + + PROJECT_VENV=$REPO_BASE/$PROJECT_NAME/.tox/$VENV + ROOTWRAP_SUDOER_CMD="$PROJECT_VENV/bin/neutron-rootwrap $PROJECT_VENV/etc/neutron/rootwrap.conf *" + ROOTWRAP_DAEMON_SUDOER_CMD="$PROJECT_VENV/bin/neutron-rootwrap-daemon $PROJECT_VENV/etc/neutron/rootwrap.conf" + TEMPFILE=$(mktemp) + cat << EOF > $TEMPFILE +# A bug in oslo.rootwrap [1] prevents commands executed with 'ip netns +# exec' from being automatically qualified with a prefix from +# rootwrap's configured exec_dirs. To work around this problem, add +# the venv bin path to a user-specific secure_path. +# +# While it might seem preferable to set a command-specific +# secure_path, this would only ensure the correct path for 'ip netns +# exec' and the command targeted for execution in the namespace would +# not inherit the path. +# +# 1: https://bugs.launchpad.net/oslo.rootwrap/+bug/1417331 +# +Defaults:$STACK_USER secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PROJECT_VENV/bin" +$STACK_USER ALL=(root) NOPASSWD: $ROOTWRAP_SUDOER_CMD +$STACK_USER ALL=(root) NOPASSWD: $ROOTWRAP_DAEMON_SUDOER_CMD +EOF + chmod 0440 $TEMPFILE + sudo chown root:root $TEMPFILE + # Name the functional testing rootwrap to ensure that it will be + # loaded after the devstack rootwrap (50_stack_sh if present) so + # that the functional testing secure_path (a superset of what + # devstack expects) will not be overwritten. + sudo mv $TEMPFILE /etc/sudoers.d/60-neutron-func-test-rootwrap +} + + +function _install_post_devstack { + echo_summary "Performing post-devstack installation" + + _install_databases + _install_rootwrap_sudoers + + if is_ubuntu; then + install_package isc-dhcp-client + install_package netcat-openbsd + elif is_fedora; then + install_package dhclient + else + exit_distro_not_supported "installing dhclient package" + fi + + # Installing python-openvswitch from packages is a stop-gap while + # python-openvswitch remains unavailable from pypi. This also + # requires that sitepackages=True be set in tox.ini to allow the + # venv to use the installed package. Once python-openvswitch + # becomes available on pypi, this will no longer be required. + # + # NOTE: the package name 'python-openvswitch' is common across + # supported distros. + install_package python-openvswitch + + enable_kernel_bridge_firewall +} + + +function _configure_iptables_rules { + # For linuxbridge agent fullstack tests we need to add special rules to + # iptables for connection of agents to rabbitmq: + CHAIN_NAME="openstack-INPUT" + sudo iptables -n --list $CHAIN_NAME 1> /dev/null 2>&1 || CHAIN_NAME="INPUT" + sudo iptables -I $CHAIN_NAME -s 240.0.0.0/8 -p tcp -m tcp -d 240.0.0.0/8 --dport 5672 -j ACCEPT +} + + +function configure_host_for_func_testing { + echo_summary "Configuring host for functional testing" + + if [[ "$INSTALL_BASE_DEPENDENCIES" == "True" ]]; then + # Installing of the following can be achieved via devstack by + # installing neutron, so their installation is conditional to + # minimize the work to do on a devstack-configured host. + _install_base_deps + _install_agent_deps + _install_rpc_backend + fi + _install_post_devstack +} + + +_init + + +if [[ "$IS_GATE" != "True" ]]; then + if [[ "$INSTALL_MYSQL_ONLY" == "True" ]]; then + _install_databases nopg + else + configure_host_for_func_testing + fi +fi + +if [[ "$VENV" =~ "dsvm-fullstack" ]]; then + _configure_iptables_rules +fi diff --git a/tox.ini b/tox.ini index 0ffdf99b5..29948a590 100644 --- a/tox.ini +++ b/tox.ini @@ -18,11 +18,38 @@ commands = # there is also secret magic in ostestr which lets you run in a fail only # mode. To do this define the TRACE_FAILONLY environmental variable. + +[testenv:common] +# Fake job to define environment variables shared between dsvm/non-dsvm jobs +setenv = OS_TEST_TIMEOUT=180 +commands = false + +[testenv:dsvm] +# Fake job to define environment variables shared between dsvm jobs +setenv = OS_SUDO_TESTING=1 + OS_ROOTWRAP_CMD=sudo {envdir}/bin/neutron-rootwrap {envdir}/etc/neutron/rootwrap.conf + OS_ROOTWRAP_DAEMON_CMD=sudo {envdir}/bin/neutron-rootwrap-daemon {envdir}/etc/neutron/rootwrap.conf + OS_FAIL_ON_MISSING_DEPS=1 + OS_LOG_PATH={env:OS_LOG_PATH:/opt/stack/logs} +commands = false + [testenv:functional] -setenv = OS_TEST_PATH=./neutron_fwaas/tests/functional +setenv = {[testenv]setenv} + {[testenv:common]setenv} + OS_TEST_PATH=./neutron_fwaas/tests/functional + OS_LOG_PATH={env:OS_LOG_PATH:/opt/stack/logs} commands = python setup.py testr --slowest --testr-args='{posargs}' +[testenv:dsvm-fullstack] +setenv = {[testenv]setenv} + {[testenv:common]setenv} + {[testenv:dsvm]setenv} + # workaround for DB teardown lock contention (bug/1541742) + OS_TEST_TIMEOUT=600 + OS_TEST_PATH=./neutron_fwaas/tests/fullstack +sitepackages=True + [testenv:api] sitepackages=True setenv =