
Fixes some new PEP8 errors that appear with jobs running on new ubuntu version, and temporarily filters out the larger I202 error ("Additional newline in a group of imports"). This patch updates the hacking and flake8-import-order versions. Copied from: https://review.opendev.org/c/openstack/ovn-octavia-provider/+/936855 Change-Id: Ice4513eedc4fd6f054c19d1854eff00aeb5c35a1
350 lines
14 KiB
Python
350 lines
14 KiB
Python
# Copyright 2020 Red Hat, Inc.
|
|
# All Rights Reserved.
|
|
#
|
|
# 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 re
|
|
import time
|
|
|
|
import netaddr
|
|
from neutron_lib import constants
|
|
from neutron_tempest_plugin.common import ip
|
|
from neutron_tempest_plugin.common import ssh
|
|
from oslo_log import log
|
|
from tempest import config
|
|
from tempest.lib.common.utils import data_utils
|
|
from tempest.lib import decorators
|
|
from tempest.lib import exceptions
|
|
|
|
from whitebox_neutron_tempest_plugin.tests.scenario import base
|
|
|
|
|
|
CONF = config.CONF
|
|
WB_CONF = CONF.whitebox_neutron_plugin_options
|
|
LOG = log.getLogger(__name__)
|
|
|
|
|
|
def get_capture_script(result_file, interface):
|
|
return """#!/bin/bash
|
|
export LC_ALL=en_US.UTF-8
|
|
tcpdump -Qin -i %(interface)s -vvneA -s0 -l icmp &> %(result_file)s &
|
|
""" % {'result_file': result_file,
|
|
'interface': interface}
|
|
|
|
|
|
# TODO(eolivare): create a parent class for BaseBroadcastTest and
|
|
# BaseMulticastTest with common code
|
|
class BaseBroadcastTest(object):
|
|
|
|
vlan_transparent = False
|
|
servers = []
|
|
|
|
# Import configuration options
|
|
receivers_count = WB_CONF.broadcast_receivers_count
|
|
|
|
capture_output_file = "/tmp/capture_broadcast_out"
|
|
# the following values can be used to send ping messages including once the
|
|
# text "Hi here"
|
|
broadcast_message = "Hi here"
|
|
broadcast_message_pattern = "486920686572652e"
|
|
ping_size = 23
|
|
|
|
@classmethod
|
|
def skip_checks(cls):
|
|
super(BaseBroadcastTest, cls).skip_checks()
|
|
advanced_image_available = (
|
|
CONF.neutron_plugin_options.advanced_image_ref or
|
|
CONF.neutron_plugin_options.default_image_is_advanced)
|
|
if not advanced_image_available:
|
|
skip_reason = "This test require advanced tools for this test"
|
|
raise cls.skipException(skip_reason)
|
|
|
|
@classmethod
|
|
def resource_setup(cls):
|
|
super(BaseBroadcastTest, cls).resource_setup()
|
|
|
|
if CONF.neutron_plugin_options.default_image_is_advanced:
|
|
cls.flavor_ref = CONF.compute.flavor_ref
|
|
cls.image_ref = CONF.compute.image_ref
|
|
cls.username = CONF.validation.image_ssh_user
|
|
else:
|
|
cls.flavor_ref = (
|
|
CONF.neutron_plugin_options.advanced_image_flavor_ref)
|
|
cls.image_ref = CONF.neutron_plugin_options.advanced_image_ref
|
|
cls.username = CONF.neutron_plugin_options.advanced_image_ssh_user
|
|
|
|
# setup basic topology for servers we can log into it
|
|
try:
|
|
if cls.vlan_transparent:
|
|
cls.network = cls.create_network(
|
|
vlan_transparent=cls.vlan_transparent)
|
|
else:
|
|
cls.network = cls.create_network()
|
|
except exceptions.ServerFault as exc:
|
|
msg = 'Backend does not support VLAN Transparency.'
|
|
if exc.resp_body['message'] == msg:
|
|
raise cls.skipException(msg)
|
|
else:
|
|
raise exc
|
|
cls.subnet = cls.create_subnet(cls.network)
|
|
cls.router = cls.create_router_by_client()
|
|
cls.create_router_interface(cls.router['id'], cls.subnet['id'])
|
|
|
|
cls.keypair = cls.create_keypair()
|
|
|
|
cls.secgroup = cls.os_primary.network_client.create_security_group(
|
|
name='secgroup_bcast')
|
|
cls.security_groups.append(cls.secgroup['security_group'])
|
|
cls.create_loginable_secgroup_rule(
|
|
secgroup_id=cls.secgroup['security_group']['id'])
|
|
cls.create_pingable_secgroup_rule(
|
|
secgroup_id=cls.secgroup['security_group']['id'])
|
|
|
|
def _create_server(self, server_type, vlan_tag=None, scheduler_hints=None):
|
|
network_id = self.network['id']
|
|
if self.vlan_transparent:
|
|
server_index = len(self.servers)
|
|
vlan_ipmask = self.vlan_ipmast_template.format(
|
|
ip_last_byte=server_index + 10)
|
|
allowed_address_pairs = [{'ip_address': vlan_ipmask}]
|
|
port = self.create_port(
|
|
network={'id': network_id},
|
|
security_groups=[self.secgroup['security_group']['id']],
|
|
allowed_address_pairs=allowed_address_pairs)
|
|
networks = [{'port': port['id']}]
|
|
else:
|
|
networks = [{'uuid': network_id}]
|
|
|
|
params = {
|
|
'flavor_ref': self.flavor_ref,
|
|
'image_ref': self.image_ref,
|
|
'key_name': self.keypair['name'],
|
|
'networks': networks,
|
|
'security_groups': [
|
|
{'name': self.secgroup['security_group']['name']}],
|
|
'name': data_utils.rand_name(server_type)
|
|
}
|
|
if not (CONF.compute.min_compute_nodes > 1):
|
|
LOG.debug('number of compute nodes is not higher than 1 - '
|
|
'scheduler_hints will not be used')
|
|
elif scheduler_hints:
|
|
params['scheduler_hints'] = scheduler_hints
|
|
server = self.create_server(**params)['server']
|
|
self.wait_for_server_active(server)
|
|
|
|
port = self.client.list_ports(device_id=server['id'])['ports'][0]
|
|
access_ip_address = self.create_floatingip(
|
|
port=port)['floating_ip_address']
|
|
|
|
server['ssh_client'] = ssh.Client(access_ip_address,
|
|
self.username,
|
|
pkey=self.keypair['private_key'])
|
|
# enable icmp broadcast responses
|
|
server['ssh_client'].execute_script(
|
|
"sysctl net.ipv4.icmp_echo_ignore_broadcasts=0", become_root=True)
|
|
|
|
if self.vlan_transparent:
|
|
# configure transparent vlan on server
|
|
vlan_tag = vlan_tag or self.vlan_tag
|
|
server['vlan_device'] = self._configure_vlan_transparent(
|
|
port, server['ssh_client'], vlan_ipmask, vlan_tag)
|
|
|
|
self.servers.append(server)
|
|
|
|
return server
|
|
|
|
def _prepare_capture_script(self, server):
|
|
interface = server['vlan_device'] if self.vlan_transparent else 'any'
|
|
capture_script = get_capture_script(
|
|
result_file=self.capture_output_file,
|
|
interface=interface)
|
|
server['ssh_client'].execute_script(
|
|
'echo "%s" > /tmp/capture_script.sh' % capture_script)
|
|
|
|
def _check_broadcast_conectivity(self, sender, receivers,
|
|
nonreceivers=[], num_pings=1):
|
|
def _message_received(client, msg, file_path):
|
|
result = client.execute_script(
|
|
"cat {path} || echo '{path} not exists yet'".format(
|
|
path=file_path))
|
|
return msg in result
|
|
|
|
def _validate_number_of_messages(client, file_path, expected_count):
|
|
"""This function validates number of packets that reached a VM
|
|
|
|
The function compares actual received number of packets per group
|
|
with the expected number.
|
|
"""
|
|
result = client.execute_script(
|
|
"cat {path} || echo '{path} not exists yet'".format(
|
|
path=file_path))
|
|
# We need to make sure that exactly the expected count
|
|
# of messages reached receiver, no more and no less
|
|
LOG.debug('result = %s', result)
|
|
count = len(
|
|
re.findall(self.broadcast_message, result))
|
|
self.assertEqual(
|
|
count, expected_count,
|
|
'Received number of messages ({}) differs '
|
|
'from the expected ({})'.format(
|
|
count, num_pings))
|
|
|
|
# tcpdump started both on receivers and nonreceivers
|
|
for server in receivers + nonreceivers:
|
|
self._prepare_capture_script(server)
|
|
server['ssh_client'].execute_script(
|
|
"bash /tmp/capture_script.sh", become_root=True)
|
|
|
|
if not self.vlan_transparent:
|
|
cidr = self.subnet['cidr']
|
|
else:
|
|
cidr = self.vlan_ipmast_template.format(ip_last_byte=0)
|
|
|
|
broadcast_ip = netaddr.IPNetwork(cidr).broadcast
|
|
# waiting until tcpdump capturing on receivers
|
|
time.sleep(2)
|
|
sender['ssh_client'].execute_script(
|
|
("ping -b %(broadcast_ip)s -s %(ping_size)d "
|
|
"-c %(num_pings)d -p %(ping_pattern)s") % {
|
|
'broadcast_ip': broadcast_ip,
|
|
'ping_size': self.ping_size,
|
|
'num_pings': num_pings,
|
|
'ping_pattern': self.broadcast_message_pattern})
|
|
# waiting until packets reached receivers
|
|
time.sleep(2)
|
|
|
|
# num_ping packets expected on each receiver server
|
|
for server in receivers:
|
|
server['ssh_client'].execute_script(
|
|
"killall tcpdump && sleep 2", become_root=True)
|
|
LOG.debug('Validating number of messages on '
|
|
'receiver %s', server['id'])
|
|
_validate_number_of_messages(
|
|
server['ssh_client'], self.capture_output_file, num_pings)
|
|
|
|
# No packets expected on each nonreceiver server
|
|
for server in nonreceivers:
|
|
server['ssh_client'].execute_script(
|
|
"killall tcpdump && sleep 2", become_root=True)
|
|
LOG.debug('Validating number of messages on '
|
|
'receiver %s', server['id'])
|
|
_validate_number_of_messages(
|
|
server['ssh_client'], self.capture_output_file, 0)
|
|
|
|
|
|
class BroadcastTestIPv4(BaseBroadcastTest, base.TrafficFlowTest):
|
|
|
|
# IP version specific parameters
|
|
_ip_version = constants.IP_VERSION_4
|
|
any_addresses = constants.IPv4_ANY
|
|
|
|
|
|
class BroadcastTestIPv4Common(BroadcastTestIPv4):
|
|
|
|
@decorators.idempotent_id('7f33370a-5f46-4452-8b2f-166acda1720f')
|
|
def test_broadcast_same_network(self):
|
|
"""Test broadcast messaging between servers on the same network
|
|
|
|
[Sender server] -> (Broadcast network) -> [Receiver server]
|
|
Scenario:
|
|
1. Create VMs for sender, receiver(s) on a common internal network
|
|
and send broadcast ping messages
|
|
2. Verify that all broadcast packets reach the other hosts
|
|
In case of vlan_transparent, broadcast packets will be captured on VLAN
|
|
interfaces
|
|
"""
|
|
sender = self._create_server('broadcast-sender')
|
|
|
|
receivers = []
|
|
for i in range(self.receivers_count):
|
|
# even VMs scheduled on a different compute from the sender
|
|
# odd VMs scheduled on the same compute than the sender
|
|
if i % 2 == 0:
|
|
scheduler_hints = {'different_host': sender['id']}
|
|
else:
|
|
scheduler_hints = {'same_host': sender['id']}
|
|
receivers.append(self._create_server(
|
|
'broadcast-receiver', scheduler_hints=scheduler_hints))
|
|
|
|
self._check_broadcast_conectivity(sender=sender,
|
|
receivers=receivers,
|
|
num_pings=2)
|
|
|
|
|
|
class BroadcastTestVlanTransparency(BroadcastTestIPv4):
|
|
|
|
required_extensions = ['vlan-transparent', 'allowed-address-pairs']
|
|
vlan_transparent = True
|
|
vlan_tag = 123
|
|
vlan_ipmast_template = '192.168.111.{ip_last_byte}/24'
|
|
|
|
def _configure_vlan_transparent(self, port, ssh_client, vlan_ip, vlan_tag):
|
|
ip_command = ip.IPCommand(ssh_client=ssh_client)
|
|
for address in ip_command.list_addresses(port=port):
|
|
port_iface = address.device.name
|
|
break
|
|
else:
|
|
self.fail("Parent port fixed IP not found on server.")
|
|
|
|
subport_iface = ip_command.configure_vlan_transparent(
|
|
port=port, vlan_tag=vlan_tag, ip_addresses=[vlan_ip])
|
|
for address in ip_command.list_addresses(ip_addresses=vlan_ip):
|
|
self.assertEqual(subport_iface, address.device.name)
|
|
self.assertEqual(port_iface, address.device.parent)
|
|
break
|
|
else:
|
|
self.fail("Sub-port fixed IP not found on server.")
|
|
|
|
return subport_iface
|
|
|
|
@decorators.idempotent_id('7ea12762-31af-4bf2-9219-c54212171010')
|
|
def test_broadcast_vlan_transparency(self):
|
|
"""Test broadcast messaging between servers on the same network, but
|
|
using different VLANs
|
|
|
|
[Sender server] -> (Broadcast network) -> [Receiver server]
|
|
Scenario:
|
|
1. Create VMs for sender, receiver(s) on a common internal network
|
|
and send broadcast ping messages
|
|
2. Verify that all broadcast packets reach the other hosts within
|
|
a common VLAN and that receivers with different VLANs do not
|
|
receive those messages
|
|
"""
|
|
vlan_groups = (self.vlan_tag, self.vlan_tag + 1)
|
|
senders = {}
|
|
receivers = {}
|
|
for vlan_group in vlan_groups:
|
|
senders[vlan_group] = self._create_server(
|
|
'broadcast-sender-%d' % vlan_group, vlan_tag=vlan_group)
|
|
receivers[vlan_group] = []
|
|
for i in range(self.receivers_count):
|
|
# even VMs scheduled on a different compute from the sender
|
|
# odd VMs scheduled on the same compute than the sender
|
|
if i % 2 == 0:
|
|
scheduler_hints = {
|
|
'different_host': senders[vlan_group]['id']}
|
|
else:
|
|
scheduler_hints = {'same_host': senders[vlan_group]['id']}
|
|
receivers[vlan_group].append(
|
|
self._create_server('broadcast-receiver-%d' % vlan_group,
|
|
vlan_tag=vlan_group,
|
|
scheduler_hints=scheduler_hints))
|
|
|
|
self._check_broadcast_conectivity(
|
|
sender=senders[vlan_groups[0]],
|
|
receivers=receivers[vlan_groups[0]],
|
|
nonreceivers=receivers[vlan_groups[1]], num_pings=2)
|
|
self._check_broadcast_conectivity(
|
|
sender=senders[vlan_groups[1]],
|
|
receivers=receivers[vlan_groups[1]],
|
|
nonreceivers=receivers[vlan_groups[0]], num_pings=2)
|