Add fullstack testing for neutron-fwaas

Add a fullstack testing framework for neutron-fwaas.  This is a basic
first pass just to get the framework deployed; detailed non-plagiaristic
tests will be added after.  For more information on what fullstack
testing is, see:

http://git.openstack.org/cgit/openstack/neutron/tree/TESTING.rst#n213

Includes definition of fullstack job in new zuul syntax.

Co-Authored-By: Reedip Banerjee <reedip14@gmail.com>
Change-Id: I16cd2432606ef9aa6b7bf3e08efe82882e5585d9
This commit is contained in:
Nate Johnston 2016-11-07 20:43:51 +00:00
parent 9346ced4b0
commit 1f971199f1
21 changed files with 2110 additions and 26 deletions

View File

@ -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

View File

@ -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.

View File

@ -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

0
neutron_fwaas/tests/contrib/gate_hook.sh Executable file → Normal file
View File

View File

@ -0,0 +1,4 @@
[[post-config|/etc/neutron/neutron.conf]]
[AGENT]
debug_iptables_rules=True

46
neutron_fwaas/tests/contrib/post_test_hook.sh Executable file → Normal file
View File

@ -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

View File

@ -0,0 +1 @@
Please see neutron/TESTING.rst for more information about what Fullstack tests are.

View File

@ -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()

View File

@ -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)

View File

@ -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': <ID of neutron port for subport>,
'segmentation_type': 'vlan',
'segmentation_id': <VLAN tag>}
: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': <ID of neutron port for subport>,
'segmentation_type': 'vlan',
'segmentation_id': <VLAN tag>}
"""
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)

View File

@ -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')

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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'}),
]

View File

@ -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

View File

@ -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 }}'

View File

@ -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

29
tox.ini
View File

@ -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 =