
For podified environments some tests will temporary enable 'shared' parameter for external network. This is because it was decided that on podified environments external network should not be shared by default for some tempest api tests to work properly, see [1]. Podified environment should run all the tests that modify external network one by one, in a single tempest thread. [1] https://github.com/openstack-k8s-operators/ci-framework/pull/1862 Change-Id: Icea7d64b1d24593135938a4f7dc242ea9a2820ff
1115 lines
49 KiB
Python
1115 lines
49 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_lib.utils import test
|
|
from neutron_tempest_plugin.common import ip
|
|
from neutron_tempest_plugin.common import ssh
|
|
from neutron_tempest_plugin.common import utils
|
|
from neutron_tempest_plugin import exceptions
|
|
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 as temp_exc
|
|
|
|
from whitebox_neutron_tempest_plugin.tests.scenario import base
|
|
|
|
|
|
CONF = config.CONF
|
|
WB_CONF = CONF.whitebox_neutron_plugin_options
|
|
LOG = log.getLogger(__name__)
|
|
PYTHON3_BIN = "python3"
|
|
INSTANCE_INTERFACE = WB_CONF.default_instance_interface
|
|
|
|
|
|
def get_receiver_script(
|
|
group, port, hello_message, ack_message, result_file, interface=None):
|
|
if interface:
|
|
bind_to_dev_cmd = """
|
|
sock.setsockopt(socket.SOL_SOCKET,
|
|
socket.SO_BINDTODEVICE,
|
|
str('%(interface)s').encode('utf-8'))""" % {
|
|
'interface': interface}
|
|
else:
|
|
bind_to_dev_cmd = ''
|
|
|
|
return """
|
|
import socket
|
|
import struct
|
|
import sys
|
|
|
|
multicast_group = '%(group)s'
|
|
server_address = ('', %(port)s)
|
|
|
|
# Create the socket
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
|
%(bind_to_dev_cmd)s
|
|
|
|
# Bind to the server address
|
|
sock.bind(server_address)
|
|
|
|
# Tell the operating system to add the socket to the multicast group
|
|
# on all interfaces.
|
|
group = socket.inet_aton(multicast_group)
|
|
mreq = struct.pack('4sL', group, socket.INADDR_ANY)
|
|
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
|
|
|
|
# Receive/respond loop
|
|
with open('%(result_file)s', 'w') as f:
|
|
f.write('%(hello_message)s')
|
|
f.flush()
|
|
data, address = sock.recvfrom(1024)
|
|
f.write('received ' + str(len(data)) + ' bytes from ' + str(address))
|
|
f.write(str(data))
|
|
sock.sendto(b'%(ack_message)s', address)
|
|
""" % {'group': group,
|
|
'port': port,
|
|
'hello_message': hello_message,
|
|
'ack_message': ack_message,
|
|
'result_file': result_file,
|
|
'bind_to_dev_cmd': bind_to_dev_cmd}
|
|
|
|
|
|
def get_sender_script(group, port, message, result_file, ttl):
|
|
|
|
return """
|
|
import socket
|
|
import sys
|
|
|
|
message = b'%(message)s'
|
|
multicast_group = ('%(group)s', %(port)s)
|
|
|
|
# Create the datagram socket
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
|
# Set the time-to-live for messages to 1 so they do not go past the
|
|
# local network segment. For east-west or north-south tests recommended
|
|
# TTL is 2.
|
|
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, %(ttl)s)
|
|
|
|
# Set a timeout so the socket does not block indefinitely when trying
|
|
# to receive data.
|
|
sock.settimeout(1)
|
|
|
|
with open('%(result_file)s', 'w') as f:
|
|
try:
|
|
# Send data to the multicast group
|
|
sent = sock.sendto(message, multicast_group)
|
|
|
|
# Look for responses from all recipients
|
|
while True:
|
|
try:
|
|
data, server = sock.recvfrom(1024)
|
|
except socket.timeout:
|
|
f.write('timed out, no more responses')
|
|
break
|
|
else:
|
|
f.write('received reply ' + str(data) + ' from ' + str(server))
|
|
finally:
|
|
sys.stdout.write('closing socket')
|
|
sock.close()
|
|
""" % {'group': group,
|
|
'port': port,
|
|
'message': message,
|
|
'result_file': result_file,
|
|
'ttl': ttl}
|
|
|
|
|
|
def get_capture_script(result_file, mcast_port, interface):
|
|
return """#!/bin/bash
|
|
export LC_ALL=en_US.UTF-8
|
|
tcpdump -Qin -i %(interface)s -vvneA -s0 -l port %(mcast_port)s \
|
|
&> %(result_file)s &
|
|
""" % {'interface': interface,
|
|
'mcast_port': mcast_port,
|
|
'result_file': result_file}
|
|
|
|
|
|
class BaseMulticastTest(object):
|
|
|
|
credentials = ['primary', 'admin']
|
|
force_tenant_isolation = False
|
|
vlan_transparent = False
|
|
|
|
# Import configuration options
|
|
receivers_count = WB_CONF.mcast_receivers_count
|
|
mcast_groups_count = WB_CONF.mcast_groups_count
|
|
external_querier_period = WB_CONF.external_igmp_querier_period
|
|
|
|
hello_message = "I am waiting..."
|
|
multicast_port = 5007
|
|
multicast_message = "group %s"
|
|
receiver_output_file = "/tmp/receiver_mcast_out"
|
|
sender_output_file = "/tmp/sender_mcast_out_%s"
|
|
capture_output_file = "/tmp/capture_mcast_out"
|
|
|
|
@classmethod
|
|
def skip_checks(cls):
|
|
super(BaseMulticastTest, 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(BaseMulticastTest, 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
|
|
if cls.has_ovn_support:
|
|
try:
|
|
cls.network = cls.create_network(
|
|
vlan_transparent=cls.vlan_transparent)
|
|
except temp_exc.ServerFault as exc:
|
|
msg = 'Backend does not support VLAN Transparency.'
|
|
if exc.resp_body['message'] == msg:
|
|
raise cls.skipException(msg)
|
|
else:
|
|
raise exc
|
|
else:
|
|
cls.network = cls.create_network()
|
|
|
|
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_mcast')
|
|
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'])
|
|
# Allow receiving of IGMP queries by VMs
|
|
cls.create_security_group_rule(
|
|
security_group_id=cls.secgroup['security_group']['id'],
|
|
protocol=constants.PROTO_NAME_IGMP,
|
|
direction=constants.INGRESS_DIRECTION)
|
|
# Create security group rule for UDP (multicast traffic)
|
|
cls.create_secgroup_rules(
|
|
rule_list=[dict(protocol=constants.PROTO_NAME_UDP,
|
|
direction=constants.INGRESS_DIRECTION,
|
|
remote_ip_prefix=cls.any_addresses,
|
|
ethertype=cls.ethertype)],
|
|
secgroup_id=cls.secgroup['security_group']['id'])
|
|
|
|
# Multicast IP range to be used for multicast group IP asignement
|
|
if '-' in cls.multicast_group_range:
|
|
multicast_group_range = netaddr.IPRange(
|
|
*cls.multicast_group_range.split('-'))
|
|
else:
|
|
multicast_group_range = netaddr.IPNetwork(
|
|
cls.multicast_group_range)
|
|
cls.multicast_group_iter = iter(multicast_group_range)
|
|
|
|
# For external or north-south topologies external network
|
|
# need to be defined
|
|
cls.external_network = cls.client.show_network(
|
|
CONF.network.public_network_id)['network']
|
|
|
|
def _check_cmd_installed_on_server(self, ssh_client, server_id, cmd):
|
|
try:
|
|
ssh_client.execute_script('which %s' % cmd)
|
|
except exceptions.SSHScriptFailed:
|
|
raise self.skipException(
|
|
"%s is not available on server %s" % (cmd, server_id))
|
|
|
|
def _prepare_sender(self, server, mcast_groups, ttl):
|
|
for mcast_group in mcast_groups:
|
|
output_file = self.sender_output_file % mcast_group
|
|
check_script = get_sender_script(
|
|
group=mcast_group, port=self.multicast_port,
|
|
message=self.multicast_message % mcast_group,
|
|
result_file=output_file,
|
|
ttl=ttl)
|
|
server['ssh_client'].execute_script(
|
|
'echo "%s" > /tmp/multicast_traffic_sender_%s.py' %
|
|
(check_script, mcast_group))
|
|
|
|
def _prepare_capture_script(self, server):
|
|
interface = (server['vlan_device'] if self.vlan_transparent
|
|
else INSTANCE_INTERFACE)
|
|
capture_script = get_capture_script(
|
|
result_file=self.capture_output_file,
|
|
mcast_port=self.multicast_port, interface=interface)
|
|
self._check_cmd_installed_on_server(
|
|
server['ssh_client'], server['id'], 'tcpdump')
|
|
server['ssh_client'].execute_script(
|
|
'echo "%s" > /tmp/capture_script.sh' % capture_script)
|
|
|
|
def _prepare_receiver(self, server, mcast_address):
|
|
interface = server['vlan_device'] if self.vlan_transparent else None
|
|
check_script = get_receiver_script(
|
|
group=mcast_address, port=self.multicast_port,
|
|
hello_message=self.hello_message, ack_message=server['id'],
|
|
result_file=self.receiver_output_file, interface=interface)
|
|
server['ssh_client'].execute_script(
|
|
'echo "%s" > /tmp/multicast_traffic_receiver.py' % check_script)
|
|
self._prepare_capture_script(server)
|
|
|
|
def _prepare_unregistered(self, server):
|
|
# We need to kill multicast receiver script if receiver becomes
|
|
# unsubscribed and the server moves to the pool of unregistered VMs
|
|
server['ssh_client'].exec_command(
|
|
"pids=$(ps ax | grep multicast | grep -v grep | awk '{print $1}')"
|
|
";kill -9 $pids && sleep 2 || true")
|
|
self._prepare_capture_script(server)
|
|
|
|
def _prepare_igmp_snooping_test(
|
|
self, mcast_groups, receivers_count=1, topology='internal',
|
|
port_type=None):
|
|
"""Function for creating desired topology for the test
|
|
|
|
Available topologies:
|
|
* internal(default): sender and receivers are on tenant network
|
|
* external: sender and receivers are on external(public) network
|
|
* east-west: sender and receivers are on different tenant networks
|
|
* north-south: sender is on external and receivers on tenant network
|
|
|
|
:param mcast_groups(list): list of multicast groups to use. Note,
|
|
for each group at least one receiver will be created.
|
|
:param receivers_count(int): Number of receivers to create for each
|
|
multicast group. Note, first receiver is always created on a
|
|
different compute node than sender is running.
|
|
:param topology(str): one of 4 available topologies to use (see list
|
|
above)
|
|
:param port_type(str): type of port to use. If omitted, default port
|
|
type will be used. Can be set to 'direct' or 'direct-physical'
|
|
for SR-IOV environments.
|
|
|
|
:returns: List of the following objects, sender, list of receivers,
|
|
list of unregistered, id of destination network
|
|
"""
|
|
# Note, for now we support port_type other than 'normal' only
|
|
# when using SR-IOV environement and therefore only external network
|
|
if topology == 'external' or topology == 'north-south':
|
|
if not self.external_network['shared']:
|
|
self.addCleanup(self.os_admin.network_client.update_network,
|
|
self.external_network['id'], shared=False)
|
|
self.os_admin.network_client.update_network(
|
|
self.external_network['id'], shared=True)
|
|
sender = self._create_server_for_topology(
|
|
network_id=self.external_network['id'],
|
|
port_type=port_type)
|
|
elif topology == 'east-west':
|
|
network2 = self.create_network()
|
|
subnet2 = self.create_subnet(network2, cidr="10.100.1.0/24")
|
|
self.create_router_interface(self.router['id'], subnet2['id'])
|
|
sender = self._create_server_for_topology(
|
|
network_id=network2['id'], port_type=port_type)
|
|
else:
|
|
sender = self._create_server_for_topology(port_type=port_type)
|
|
|
|
if topology == 'external':
|
|
dst_network_id = self.external_network['id']
|
|
else:
|
|
dst_network_id = self.network['id']
|
|
|
|
receivers = []
|
|
for group_id in range(len(mcast_groups)):
|
|
if receivers_count > 0:
|
|
# At least one receiver in the group we need to be launched
|
|
# on different host
|
|
receivers.append(
|
|
[self._create_server_for_topology(
|
|
different_host=sender, network_id=dst_network_id,
|
|
port_type=port_type)])
|
|
for _ in range(receivers_count - 1):
|
|
receivers[group_id].append(
|
|
self._create_server_for_topology(
|
|
network_id=dst_network_id, port_type=port_type))
|
|
else:
|
|
# For some tests we do not create receivers
|
|
receivers.append([])
|
|
|
|
servers = [item for sublist in receivers for item in sublist]
|
|
servers.append(sender)
|
|
|
|
unregistered = self._create_server_for_topology(
|
|
network_id=dst_network_id, port_type=port_type)
|
|
servers.append(unregistered)
|
|
# For some tests we need to support more than one unregistered server
|
|
unregistered = [unregistered]
|
|
|
|
for server in servers:
|
|
self._check_cmd_installed_on_server(server['ssh_client'],
|
|
server['id'], PYTHON3_BIN)
|
|
return [sender, receivers, unregistered, dst_network_id]
|
|
|
|
def restart_openvswitch_on_compute_nodes(self):
|
|
for node in self.nodes:
|
|
if node['is_compute'] is True and node['is_controller'] is False:
|
|
self.reset_node_service('ovs vswitchd', node['client'])
|
|
|
|
def _is_multicast_traffic_expected(self, mcast_address):
|
|
"""Checks if multicast traffic is expected to arrive.
|
|
|
|
Checks if multicast traffic is expected to arrive to the
|
|
unregistered VM.
|
|
|
|
If IGMP snooping is enabled, multicast traffic should not be
|
|
flooded unless the destination IP is in the range of 224.0.0.X
|
|
[0].
|
|
|
|
[0] https://tools.ietf.org/html/rfc4541 (See section 2.1.2)
|
|
"""
|
|
return (str(mcast_address).startswith('224.0.0') or not
|
|
CONF.neutron_plugin_options.is_igmp_snooping_enabled)
|
|
|
|
def _check_multicast_conectivity(
|
|
self, mcast_groups, sender, receivers, unregistered,
|
|
test_unsubscribe=False, start_delay=None, pre_action=None, ttl=1):
|
|
"""Test multicast messaging between servers
|
|
|
|
[Sender server] -> ... some network topology ... -> [Receiver server]
|
|
|
|
:param mcast_groups(list): list of multicast groups to use.
|
|
Note: for each group will be created a sender script and at least
|
|
one receiver.
|
|
:param sender(server): server that will send multicast traffic
|
|
:param receivers(list): list of lists of servers. There is ia list of
|
|
receivers per group.
|
|
:param unregistered(list): list of servers that are not subscribed
|
|
to receive multicast traffic.
|
|
:param test_unsubscribe(boolean): whether to test unsubsribe step or
|
|
not. Default is False.
|
|
:param start_delay(int): whether to include delay waiting step before
|
|
sending packets and how long (in seconds). Default is no delay.
|
|
:param pre_action(callable): run additional arbitrary action before
|
|
sending multicast packets.
|
|
:param ttl(int): time-to-live for multicast packages. Default is 1 fits
|
|
well when all VMs are on the same network. For testing multicast
|
|
between 2 different network TTL should be set to 2.
|
|
|
|
Scenario is the following:
|
|
1. Install required scripts on all VMs according their roles.
|
|
2. Send multicast packets from sender to each multicast group.
|
|
Note: by default a single group is used and single receiver,
|
|
but this can be adjusted with config options.
|
|
3. Verify that multicast traffic reached each receiver and
|
|
does not reach unregistgered host. Number of received packets
|
|
is the same as sent number.
|
|
4. Unsubscribe one receiver in each group and repeat steps 2 and 3.
|
|
Note: in case more than one receiver configured the step will
|
|
repeat until no subscribed hosts left.
|
|
|
|
"""
|
|
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, mcast_groups, file_path, allowed_group=None):
|
|
"""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 = {}'. format(result))
|
|
for mcast_group in mcast_groups:
|
|
LOG.debug('Validating group {}'.format(mcast_group))
|
|
if allowed_group:
|
|
LOG.debug('Allowed group {}'.format(allowed_group))
|
|
if ((allowed_group and mcast_group == allowed_group) or
|
|
self._is_multicast_traffic_expected(mcast_group)):
|
|
expected_count = 1
|
|
else:
|
|
expected_count = 0
|
|
count = len(
|
|
re.findall(self.multicast_message % mcast_group, result))
|
|
self.assertEqual(
|
|
count, expected_count,
|
|
'Received number of messages ({}) to group {} differs '
|
|
'from the expected ({})'.format(
|
|
count, mcast_group, expected_count))
|
|
|
|
def _test_traffic_between_servers():
|
|
for server in unregistered:
|
|
self._prepare_unregistered(server)
|
|
server['ssh_client'].execute_script(
|
|
"bash /tmp/capture_script.sh", become_root=True)
|
|
|
|
receiver_ids = []
|
|
# receivers is a list of separate lists for each mcast group
|
|
group_ids = range(len(mcast_groups))
|
|
for group_id in group_ids:
|
|
receiver_ids.append([])
|
|
for receiver in receivers[group_id]:
|
|
self._prepare_receiver(
|
|
receiver, mcast_groups[group_id])
|
|
# We run the capture script on each receiver as well
|
|
# in order to check that there is no traffic from any
|
|
# group that the receiver did not subscribe to
|
|
receiver['ssh_client'].execute_script(
|
|
"bash /tmp/capture_script.sh", become_root=True)
|
|
|
|
# receiver script needs to be executed as root user for
|
|
# vlan_transparency in order to bind the vlan interface
|
|
receiver['ssh_client'].execute_script(
|
|
"%s /tmp/multicast_traffic_receiver.py &" %
|
|
PYTHON3_BIN, shell="bash",
|
|
become_root=self.vlan_transparent)
|
|
utils.wait_until_true(
|
|
lambda: _message_received(
|
|
receiver['ssh_client'], self.hello_message,
|
|
self.receiver_output_file),
|
|
exception=RuntimeError(
|
|
"Receiver script didn't start properly on server "
|
|
"{!r}.".format(receiver['id'])))
|
|
receiver_ids[group_id].append(receiver['id'])
|
|
|
|
if pre_action:
|
|
pre_action()
|
|
|
|
if start_delay and start_delay > 5:
|
|
LOG.debug(
|
|
"Waiting {} seconds for start delay to expire".format(
|
|
start_delay))
|
|
time.sleep(start_delay)
|
|
else:
|
|
# (rsafrono) Note, this delay is needed to make sure all
|
|
# receivers are ready. In some cases (e.g. SR-IOV) test result
|
|
# is unstable if we start to send traffic without this delay.
|
|
time.sleep(10)
|
|
|
|
for group in mcast_groups:
|
|
LOG.debug("Starting script for group {} on "
|
|
"sender".format(group))
|
|
sender['ssh_client'].execute_script(
|
|
"%s /tmp/multicast_traffic_sender_%s.py" % (
|
|
PYTHON3_BIN, group))
|
|
|
|
for group_id in group_ids:
|
|
for receiver in receivers[group_id]:
|
|
utils.wait_until_true(
|
|
lambda: _message_received(
|
|
receiver['ssh_client'],
|
|
self.multicast_message % mcast_groups[group_id],
|
|
self.receiver_output_file),
|
|
exception=RuntimeError(
|
|
"Receiver {!r} didn't get multicast "
|
|
"message".format(receiver['id'])))
|
|
|
|
receiver['ssh_client'].execute_script(
|
|
"killall tcpdump && sleep 2", become_root=True)
|
|
LOG.debug('Validating number of messages on '
|
|
'receiver {}'.format(receiver['id']))
|
|
_validate_number_of_messages(
|
|
receiver['ssh_client'], mcast_groups,
|
|
self.capture_output_file, mcast_groups[group_id])
|
|
|
|
for group_id in group_ids:
|
|
sender_output = (
|
|
self.sender_output_file % mcast_groups[group_id])
|
|
replies_result = sender['ssh_client'].execute_script(
|
|
"cat {path} || echo '{path} not exists yet'".format(
|
|
path=sender_output))
|
|
for receiver_id in receiver_ids[group_id]:
|
|
self.assertIn(receiver_id, replies_result)
|
|
|
|
for server in unregistered:
|
|
server['ssh_client'].execute_script(
|
|
"killall tcpdump && sleep 2", become_root=True)
|
|
LOG.debug('Validating number of messages on '
|
|
'unregistered {}'.format(server['id']))
|
|
_validate_number_of_messages(
|
|
server['ssh_client'], mcast_groups,
|
|
self.capture_output_file)
|
|
|
|
def _unsubscribe_receiver_from_group(group_id):
|
|
if len(receivers[group_id]) > 0:
|
|
unregistered.append(receivers[group_id][-1])
|
|
del receivers[group_id][-1]
|
|
|
|
self._prepare_sender(sender, mcast_groups, ttl)
|
|
_test_traffic_between_servers()
|
|
# If test_unsubscribe option is enabled then we unsubscribe
|
|
# one receiver from each group and retest traffic.
|
|
# Repeat this step until all receivers are unsubscribed.
|
|
if test_unsubscribe:
|
|
while len(receivers[0]) > 0:
|
|
for group_id in range(len(mcast_groups)):
|
|
_unsubscribe_receiver_from_group(group_id)
|
|
_test_traffic_between_servers()
|
|
|
|
|
|
class MulticastTestIPv4(BaseMulticastTest, base.TrafficFlowTest):
|
|
|
|
# Import configuration options
|
|
multicast_group_range = CONF.neutron_plugin_options.multicast_group_range
|
|
|
|
# IP version specific parameters
|
|
_ip_version = constants.IP_VERSION_4
|
|
any_addresses = constants.IPv4_ANY
|
|
|
|
|
|
class MulticastTestIPv4Common(MulticastTestIPv4):
|
|
@decorators.idempotent_id('615a35d3-642e-4440-a19e-351f29d0b3ff')
|
|
def test_igmp_snooping_same_network_and_unsubscribe(self):
|
|
"""Test multicast messaging between servers on the same network
|
|
|
|
[Sender server] -> (Multicast network) -> [Receiver server]
|
|
Scenario:
|
|
1. Create VMs for sender, receiver(s), unregistered host.
|
|
Note: default(internal, same network) topology is used.
|
|
mcast_groups contain only groups where no flooding
|
|
to all ports expected.
|
|
2. Verify that multicast packets reach subscribed hosts
|
|
and do not reach unsubscribed. For more details see
|
|
_check_multicast_connectivity document text and code.
|
|
"""
|
|
mcast_groups = [next(self.multicast_group_iter)
|
|
for _ in range(self.mcast_groups_count)]
|
|
sender, receivers, unregistered, _ = self._prepare_igmp_snooping_test(
|
|
mcast_groups=mcast_groups,
|
|
receivers_count=self.receivers_count)
|
|
self._check_multicast_conectivity(
|
|
mcast_groups=mcast_groups, sender=sender, receivers=receivers,
|
|
unregistered=unregistered, test_unsubscribe=True)
|
|
|
|
@decorators.idempotent_id('fcd50103-eeba-475f-803b-dfc9e8c342a5')
|
|
def test_igmp_snooping_ext_network_and_unsubscribe(self):
|
|
"""Test multicast between VMs on external network
|
|
|
|
[Sender server] -> (Multicast network) -> [Receiver server]
|
|
Scenario:
|
|
1. Create VMs for sender, receiver(s), unregistered host.
|
|
Note: External topology is used.
|
|
mcast_groups contain only groups where no flooding
|
|
to all ports expected.
|
|
2. Verify that multicast packets reach subscribed hosts
|
|
and do not reach unsubscribed. For more details see
|
|
_check_multicast_connectivity document text and code.
|
|
"""
|
|
mcast_groups = [next(self.multicast_group_iter)
|
|
for _ in range(self.mcast_groups_count)]
|
|
sender, receivers, unregistered, _ = self._prepare_igmp_snooping_test(
|
|
mcast_groups=mcast_groups, topology='external',
|
|
receivers_count=self.receivers_count)
|
|
self._check_multicast_conectivity(
|
|
mcast_groups=mcast_groups, sender=sender, receivers=receivers,
|
|
unregistered=unregistered, test_unsubscribe=True)
|
|
|
|
@decorators.idempotent_id('35523bd5-4b35-4e6b-a25a-20826808d85d')
|
|
def test_flooding_when_special_groups(self):
|
|
"""Test that multicast traffic of groups 224.0.0.X is flooded
|
|
|
|
Scenario:
|
|
1. Create VMs for sender, receiver(s), unregistered host.
|
|
Note: default(internal, same network) topology is used.
|
|
mcast_groups contains only groups from the range 224.0.0.X
|
|
2. Verify that multicast packets reach all VMs, even
|
|
unregistered ones.
|
|
"""
|
|
mcast_groups = ['224.0.0.100']
|
|
sender, receivers, unregistered, _ = self._prepare_igmp_snooping_test(
|
|
mcast_groups=mcast_groups)
|
|
self._check_multicast_conectivity(
|
|
mcast_groups=mcast_groups,
|
|
sender=sender, receivers=receivers, unregistered=unregistered)
|
|
|
|
@decorators.idempotent_id('42bfc8e1-0efb-4573-bb4a-290060a40980')
|
|
def test_igmp_snooping_after_openvswitch_restart(self):
|
|
"""Test IGMP snooping after openvswitch restart
|
|
|
|
[Sender VM] -> (Multicast network) -> [Receiver VM]
|
|
|
|
Note: this test should not be run with other tests in parallel
|
|
because it can affect other tests results.
|
|
|
|
Scenario:
|
|
1. Create VMs for sender, receiver(s), unregistered host.
|
|
mcast_groups contain only groups where no flooding
|
|
to all ports expected.
|
|
2. Subscribe receiver VMs to a multicast group but do not
|
|
send multicast packets immediately.
|
|
3. Restart Openvswitch on compute nodes.
|
|
4. Send multicast traffic and verify that multicast packets
|
|
reach subscribed hosts and do not reach unsubscribed. For more
|
|
details see _check_multicast_connectivity document text and code.
|
|
"""
|
|
if not hasattr(self, 'nodes'):
|
|
raise self.skipException(
|
|
"Nodes info not available. Test won't be able to restart "
|
|
"openvswitch service on a node.")
|
|
if len(self.nodes) == 1:
|
|
raise self.skipException(
|
|
"This test is not supported on a single-node environment")
|
|
mcast_groups = [next(self.multicast_group_iter)
|
|
for _ in range(self.mcast_groups_count)]
|
|
sender, receivers, unregistered, _ = self._prepare_igmp_snooping_test(
|
|
mcast_groups=mcast_groups,
|
|
receivers_count=self.receivers_count)
|
|
self._check_multicast_conectivity(
|
|
mcast_groups=mcast_groups, sender=sender, receivers=receivers,
|
|
unregistered=unregistered,
|
|
pre_action=self.restart_openvswitch_on_compute_nodes)
|
|
|
|
|
|
class MulticastTestIPv4Sriov(MulticastTestIPv4):
|
|
|
|
@classmethod
|
|
def resource_setup(cls):
|
|
super(MulticastTestIPv4Sriov, cls).resource_setup()
|
|
if not cls.has_sriov_support:
|
|
skip_reason = "Environment does not support SR-IOV"
|
|
raise cls.skipException(skip_reason)
|
|
|
|
def _validate_pfs_availability(self):
|
|
required_sriov_ports = (
|
|
self.mcast_groups_count * self.receivers_count + 2)
|
|
available_sriov_ports = (
|
|
WB_CONF.sriov_pfs_per_host *
|
|
len([node for node in self.nodes
|
|
if node['is_compute'] is True]))
|
|
if (available_sriov_ports < required_sriov_ports):
|
|
self.skipTest(
|
|
'Not enough SR-IOV ports ({}), while required {}'.format(
|
|
available_sriov_ports, required_sriov_ports))
|
|
|
|
|
|
class MulticastTestIPv4SriovCommon(MulticastTestIPv4Sriov):
|
|
|
|
@decorators.idempotent_id('4210438f-080b-4254-a7d1-166144f04572')
|
|
def test_igmp_snooping_ext_network_with_vf_ports(self):
|
|
"""Test multicast between VMs with SR-IOV VF ports on external network
|
|
|
|
[Sender with VF port] -> (External network) -> [Receiver with VF port]
|
|
Note: the test runs only on SR-IOV environment and requires shared
|
|
external network.
|
|
|
|
Scenario:
|
|
1. Create VMs for sender, receiver(s), unregistered host.
|
|
Note: External topology is used. External network must be shared.
|
|
mcast_groups contain only groups where no flooding
|
|
to all ports expected.
|
|
2. Verify that multicast packets reach subscribed hosts
|
|
and do not reach unsubscribed. For more details see
|
|
_check_multicast_connectivity document text and code.
|
|
"""
|
|
mcast_groups = [next(self.multicast_group_iter)
|
|
for _ in range(self.mcast_groups_count)]
|
|
sender, receivers, unregistered, _ = self._prepare_igmp_snooping_test(
|
|
mcast_groups=mcast_groups, topology='external',
|
|
port_type='direct', receivers_count=self.receivers_count)
|
|
self._check_multicast_conectivity(
|
|
mcast_groups=mcast_groups, sender=sender, receivers=receivers,
|
|
unregistered=unregistered, test_unsubscribe=True)
|
|
|
|
@decorators.idempotent_id('86fb3e4b-5e01-4953-99a9-4f85c561f57e')
|
|
def test_igmp_snooping_ext_network_with_pf_ports(self):
|
|
"""Test multicast between VMs with SR-IOV PF ports on external network
|
|
|
|
[Sender with PF port] -> (External network) -> [Receiver with PF port]
|
|
Note: the test runs only on SR-IOV environment and requires shared
|
|
external network.
|
|
|
|
Scenario:
|
|
1. Create VMs for sender, receiver(s), unregistered host.
|
|
Note: External topology is used. External network must be shared.
|
|
mcast_groups contain only groups where no flooding
|
|
to all ports expected.
|
|
2. Verify that multicast packets reach subscribed hosts
|
|
and do not reach unsubscribed. For more details see
|
|
_check_multicast_connectivity document text and code.
|
|
"""
|
|
mcast_groups = [next(self.multicast_group_iter)
|
|
for _ in range(self.mcast_groups_count)]
|
|
self._validate_pfs_availability()
|
|
sender, receivers, unregistered, _ = self._prepare_igmp_snooping_test(
|
|
mcast_groups=mcast_groups, topology='external',
|
|
port_type='direct-physical', receivers_count=self.receivers_count)
|
|
self._check_multicast_conectivity(
|
|
mcast_groups=mcast_groups, sender=sender, receivers=receivers,
|
|
unregistered=unregistered, test_unsubscribe=True)
|
|
|
|
|
|
class MulticastTestIPv4OvnBase(MulticastTestIPv4, base.BaseTempestTestCaseOvn):
|
|
mcast_idle_timeout = 60
|
|
|
|
# We will use this function in order to decrease the idle timeout from the
|
|
# default value in order to save some time
|
|
def set_mcast_idle_timeout(self, network_id):
|
|
uuid = self.get_item_uuid(
|
|
db='nb', item='logical_switch',
|
|
search_string='name=neutron-' + network_id)
|
|
cmd = self.nbctl + " set logical_switch " + uuid + " other_config:"
|
|
self.run_on_master_controller(
|
|
cmd + "mcast_idle_timeout=" + str(self.mcast_idle_timeout))
|
|
|
|
def set_querier_on_network(self, network_id):
|
|
port = self.client.list_ports(
|
|
network_id=network_id,
|
|
device_owner=constants.DEVICE_OWNER_DHCP)['ports']
|
|
eth_src = port[0]['mac_address']
|
|
ip4_src = port[0]['fixed_ips'][0]['ip_address']
|
|
uuid = self.get_item_uuid(
|
|
db='nb', item='logical_switch',
|
|
search_string='name=neutron-' + network_id)
|
|
cmd = self.nbctl + " set logical_switch " + uuid + " other_config:"
|
|
self.run_on_master_controller(
|
|
cmd + "mcast_querier='true';" +
|
|
cmd + "mcast_eth_src=" + eth_src + ";" +
|
|
cmd + "mcast_ip4_src=" + ip4_src)
|
|
|
|
def set_mcast_relay_on_router(self, router_id, state='true'):
|
|
uuid = self.get_item_uuid(
|
|
db='nb', item='logical_router',
|
|
search_string='name=neutron-' + router_id)
|
|
self.run_on_master_controller(
|
|
self.nbctl + " set logical_router " + uuid +
|
|
" options:mcast_relay=" + state)
|
|
|
|
def restart_ovn_controller_on_compute_nodes(self):
|
|
for node in self.nodes:
|
|
if node['is_compute'] is True and node['is_controller'] is False:
|
|
self.reset_node_service('ovn controller', node['client'])
|
|
|
|
|
|
class MulticastTestIPv4Ovn(MulticastTestIPv4OvnBase):
|
|
|
|
@test.unstable_test("querier not available by default, see RHBZ 1791815")
|
|
@decorators.idempotent_id('fa082cf9-37fc-4e7f-bfdb-fbd8e6860634')
|
|
def test_multicast_after_idle_timeout(self):
|
|
"""Test multicast messaging after idle timeout
|
|
|
|
[Sender VM] -> (Multicast network) -> [Receiver VM]
|
|
|
|
Scenario:
|
|
1. Create VMs for sender, receiver(s), unregistered host.
|
|
mcast_groups contain only groups where no flooding
|
|
to all ports expected.
|
|
2. Subscribe receiver VMs to a multicast group but do not send
|
|
multicast packets immediately. Wait first until idle_timeout
|
|
expires and send packets afterwards.
|
|
3. Verify that multicast packets reach subscribed hosts
|
|
and do not reach unsubscribed. For more details see
|
|
_check_multicast_connectivity document text and code.
|
|
"""
|
|
mcast_groups = [next(self.multicast_group_iter)
|
|
for _ in range(self.mcast_groups_count)]
|
|
sender, receivers, unregistered, dst_network_id = (
|
|
self._prepare_igmp_snooping_test(
|
|
mcast_groups=mcast_groups,
|
|
receivers_count=self.receivers_count))
|
|
self.set_mcast_idle_timeout(dst_network_id)
|
|
self._check_multicast_conectivity(
|
|
mcast_groups=mcast_groups, sender=sender, receivers=receivers,
|
|
unregistered=unregistered, start_delay=self.mcast_idle_timeout)
|
|
|
|
# (rsafrono) Note, this test is temporary. It includes enabling querier
|
|
# as a workaround because currently the querier is not enabled by default,
|
|
# see RHBZ 1791815
|
|
@test.unstable_test("Multicast querier not yet supported officially")
|
|
@decorators.idempotent_id('be5e153d-1ce7-4d19-9efd-2aae0ec74749')
|
|
def test_idle_timeout_with_querier_enabled(self):
|
|
"""Test multicast messaging after idle timeout when querier is enabled
|
|
|
|
[Sender VM] -> (Multicast network) -> [Receiver VM]
|
|
|
|
Scenario:
|
|
1. Create VMs for sender, receiver(s), unregistered host.
|
|
mcast_groups contain only groups where no flooding
|
|
to all ports expected.
|
|
2. Enable querier on the network. Subscribe receiver VMs to
|
|
a multicast group but do not send multicast packets immediately.
|
|
Wait first until idle_timeout expires and send afterwards.
|
|
3. Verify that multicast packets reach subscribed hosts
|
|
and do not reach unsubscribed. For more details see
|
|
_check_multicast_connectivity document text and code.
|
|
"""
|
|
|
|
mcast_groups = [next(self.multicast_group_iter)
|
|
for _ in range(self.mcast_groups_count)]
|
|
sender, receivers, unregistered, dst_network_id = (
|
|
self._prepare_igmp_snooping_test(
|
|
mcast_groups=mcast_groups,
|
|
receivers_count=self.receivers_count))
|
|
self.set_querier_on_network(dst_network_id)
|
|
self.set_mcast_idle_timeout(dst_network_id)
|
|
self._check_multicast_conectivity(
|
|
mcast_groups=mcast_groups, sender=sender, receivers=receivers,
|
|
unregistered=unregistered, start_delay=self.mcast_idle_timeout)
|
|
|
|
# (rsafrono) Note, this test includes enabling mcast_relay as
|
|
# a workaround. Currently mcast_relay is not enabled on router by default.
|
|
@test.unstable_test("mcast relay not yet supported officially")
|
|
@decorators.idempotent_id('3a906cd8-e27a-40a7-a369-829a7ec91af6')
|
|
def test_multicast_east_west(self):
|
|
"""Test multicast between servers on different tenant networks
|
|
|
|
[Sender VM] [Receiver VM]
|
|
| |
|
|
[Internal network A] -> [Router] -> [Internal network B]
|
|
|
|
Scenario:
|
|
1. Create VMs for sender, receiver(s), unregistered host.
|
|
Note: East-west topology is used (2 internal networks)
|
|
mcast_groups contain only groups where no flooding
|
|
to all ports expected.
|
|
2. Verify that multicast packets reach subscribed hosts
|
|
and do not reach unsubscribed. For more details see
|
|
_check_multicast_connectivity document text and code.
|
|
"""
|
|
self.set_mcast_relay_on_router(self.router['id'])
|
|
mcast_groups = [next(self.multicast_group_iter)
|
|
for _ in range(self.mcast_groups_count)]
|
|
sender, receivers, unregistered, _ = (
|
|
self._prepare_igmp_snooping_test(
|
|
mcast_groups=mcast_groups, topology='east-west',
|
|
receivers_count=self.receivers_count))
|
|
self._check_multicast_conectivity(
|
|
mcast_groups=mcast_groups, sender=sender, receivers=receivers,
|
|
unregistered=unregistered, ttl=2)
|
|
|
|
# (rsafrono) Note, this test includes enabling mcast_relay as
|
|
# a workaround. Currently mcast_relay is not enabled on router by default.
|
|
@test.unstable_test("Core OVN bug, see RHBZ 1902075")
|
|
@decorators.idempotent_id('71abfc10-3f6c-4096-a1d3-8fd934b5e3ba')
|
|
def test_multicast_north_south(self):
|
|
"""Test multicast between VMs on external and internal networks
|
|
|
|
[Sender VM] -> [External network]
|
|
|
|
|
[Router]
|
|
|
|
|
[Receiver VM] <- [Internal network]
|
|
|
|
Note: the test requires shared external network.
|
|
Scenario:
|
|
1. Create VMs for sender, receiver(s), unregistered host.
|
|
Note: North-south topology is used where sender is on
|
|
the external network and receivers on the internal one.
|
|
mcast_groups contain only groups where no flooding
|
|
to all ports expected.
|
|
2. Verify that multicast packets reach subscribed hosts
|
|
and do not reach unsubscribed. For more details see
|
|
_check_multicast_connectivity document text and code.
|
|
"""
|
|
self.set_mcast_relay_on_router(self.router['id'])
|
|
mcast_groups = [next(self.multicast_group_iter)
|
|
for _ in range(self.mcast_groups_count)]
|
|
sender, receivers, unregistered, _ = (
|
|
self._prepare_igmp_snooping_test(
|
|
mcast_groups=mcast_groups, topology='north-south',
|
|
receivers_count=self.receivers_count))
|
|
self._check_multicast_conectivity(
|
|
mcast_groups=mcast_groups, sender=sender, receivers=receivers,
|
|
unregistered=unregistered, ttl=2)
|
|
|
|
|
|
class MulticastTestIPv4SriovOvn(
|
|
MulticastTestIPv4Sriov, MulticastTestIPv4OvnBase):
|
|
|
|
# (rsafrono) External IGMP querier required for this test.
|
|
# For now the only reliable option for this is to use SR-IOV environment.
|
|
@decorators.idempotent_id('fb56ac55-7863-44f9-b0e4-3383093838a2')
|
|
def test_after_ovn_controller_restart_with_external_querier(self):
|
|
"""Test IGMP snooping after ovn controller restart
|
|
|
|
[Sender VM] -> (Multicast network) -> [Receiver VM]
|
|
|
|
Notes:
|
|
1. External IGMP querier required for this test to run properly.
|
|
2. This test should not be run with other tests in parallel
|
|
because it can affect other tests results.
|
|
|
|
Scenario:
|
|
1. Create VMs for sender, receiver(s), unregistered host.
|
|
mcast_groups contain only groups where no flooding
|
|
to all ports expected.
|
|
2. Subscribe receiver VMs to a multicast group but do not
|
|
send multicast packets immediately.
|
|
3. Restart OVN controller on compute nodes. This will cause that
|
|
OVN controller unlearns the multicast groups.
|
|
4. Wait until external querier send new queries, receivers
|
|
VMs will respond to queries and OVN controller will re-learn
|
|
the multicast groups.
|
|
5. Send multicast traffic and verify that multicast packets
|
|
reach subscribed hosts and do not reach unsubscribed. For more
|
|
details see _check_multicast_connectivity document text and code.
|
|
"""
|
|
|
|
mcast_groups = [next(self.multicast_group_iter)
|
|
for _ in range(self.mcast_groups_count)]
|
|
sender, receivers, unregistered, dst_network_id = (
|
|
self._prepare_igmp_snooping_test(
|
|
port_type='direct',
|
|
mcast_groups=mcast_groups, topology='external',
|
|
receivers_count=self.receivers_count))
|
|
self._check_multicast_conectivity(
|
|
mcast_groups=mcast_groups, sender=sender, receivers=receivers,
|
|
unregistered=unregistered,
|
|
start_delay=self.external_querier_period,
|
|
pre_action=self.restart_ovn_controller_on_compute_nodes)
|
|
|
|
|
|
class MulticastTestVlanTransparency(MulticastTestIPv4):
|
|
|
|
required_extensions = ['vlan-transparent', 'allowed-address-pairs']
|
|
vlan_transparent = True
|
|
vlan_tag = 123
|
|
vlan_ipmast_template = '192.168.123.{ip_last_byte}/24'
|
|
|
|
# initialize server index: this index will be used for allowed_address_pair
|
|
# values
|
|
server_index = 0
|
|
|
|
def _prepare_igmp_snooping_test_vlan_transparency(
|
|
self, mcast_groups, receivers_count=1, topology='internal',
|
|
port_type=None):
|
|
|
|
sender = self._create_multicast_server_vlan_transparency(mcast_groups)
|
|
|
|
receivers = []
|
|
for group_id in range(len(mcast_groups)):
|
|
if receivers_count > 0:
|
|
# At least one receiver in the group we need to be launched
|
|
# on different host
|
|
receivers.append(
|
|
[self._create_multicast_server_vlan_transparency(
|
|
mcast_groups, different_host=sender)])
|
|
for _ in range(receivers_count - 1):
|
|
receivers[group_id].append(
|
|
self._create_multicast_server_vlan_transparency(
|
|
mcast_groups))
|
|
else:
|
|
# For some tests we do not create receivers
|
|
receivers.append([])
|
|
|
|
servers = [item for sublist in receivers for item in sublist]
|
|
servers.append(sender)
|
|
|
|
unregistered = self._create_multicast_server_vlan_transparency(
|
|
mcast_groups)
|
|
servers.append(unregistered)
|
|
# For some tests we need to support more than one unregistered server
|
|
unregistered = [unregistered]
|
|
|
|
for server in servers:
|
|
self._check_cmd_installed_on_server(server['ssh_client'],
|
|
server['id'], PYTHON3_BIN)
|
|
return [sender, receivers, unregistered]
|
|
|
|
def _create_multicast_server_vlan_transparency(
|
|
self, mcast_groups, different_host=None):
|
|
vlan_ipmask = self.vlan_ipmast_template.format(
|
|
ip_last_byte=self.server_index + 10)
|
|
self.server_index += 1
|
|
allowed_address_pairs = [{'ip_address': vlan_ipmask}]
|
|
port = self.create_port(
|
|
network={'id': self.network['id']},
|
|
security_groups=[self.secgroup['security_group']['id']],
|
|
allowed_address_pairs=allowed_address_pairs)
|
|
networks = [{'port': port['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('multicast-server-vlan-transparent')
|
|
}
|
|
if (different_host and CONF.compute.min_compute_nodes > 1):
|
|
params['scheduler_hints'] = {
|
|
'different_host': different_host['id']}
|
|
server = self.create_server(**params)['server']
|
|
|
|
# create fip
|
|
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'])
|
|
|
|
# configure transparent vlan on server
|
|
server['vlan_device'] = self._configure_vlan_transparent(
|
|
port, server['ssh_client'], vlan_ipmask, mcast_groups)
|
|
|
|
return server
|
|
|
|
def _configure_vlan_transparent(
|
|
self, port, ssh_client, vlan_ip, mcast_groups):
|
|
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=self.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.")
|
|
|
|
for mcast_group in mcast_groups:
|
|
ip_command.add_route(mcast_group, subport_iface)
|
|
|
|
return subport_iface
|
|
|
|
@decorators.idempotent_id('c480cec8-3ca4-4781-baad-2e1190079467')
|
|
def test_igmp_snooping_vlan_transparency(self):
|
|
"""Test multicast messaging between servers on the same network
|
|
|
|
[Sender server] -> (Multicast network) -> [Receiver server]
|
|
Scenario:
|
|
1. Create VMs for sender, receiver(s), unregistered host.
|
|
Note: default(internal, same network) topology is used.
|
|
mcast_groups contain only groups where no flooding
|
|
to all ports expected.
|
|
2. Verify that multicast packets reach subscribed hosts
|
|
and do not reach unsubscribed. For more details see
|
|
_check_multicast_connectivity document text and code.
|
|
"""
|
|
mcast_groups = [next(self.multicast_group_iter)
|
|
for _ in range(self.mcast_groups_count)]
|
|
sender, receivers, unregistered = (
|
|
self._prepare_igmp_snooping_test_vlan_transparency(
|
|
mcast_groups=mcast_groups,
|
|
receivers_count=self.receivers_count))
|
|
self._check_multicast_conectivity(
|
|
mcast_groups=mcast_groups, sender=sender, receivers=receivers,
|
|
unregistered=unregistered, test_unsubscribe=False)
|