blob: a39a0c3dfe3c7afde70330e7bb9849e00400af06 [file] [log] [blame]
Federico Ressia2aad942018-04-09 12:01:48 +02001# Copyright 2018 Red Hat, Inc.
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
16import netaddr
17from neutron_lib import constants
Rodolfo Alonso Hernandez4dea8062020-01-16 16:32:59 +000018from neutron_lib.utils import test
Federico Ressia2aad942018-04-09 12:01:48 +020019from oslo_log import log
20from tempest.lib.common.utils import data_utils
21from tempest.lib import decorators
22
23from neutron_tempest_plugin.common import ssh
24from neutron_tempest_plugin.common import utils
25from neutron_tempest_plugin import config
Slawek Kaplonski2f467ca2019-09-05 16:28:09 +020026from neutron_tempest_plugin import exceptions
Federico Ressia2aad942018-04-09 12:01:48 +020027from neutron_tempest_plugin.scenario import base
28
29
30CONF = config.CONF
31LOG = log.getLogger(__name__)
Slawek Kaplonski2f467ca2019-09-05 16:28:09 +020032PYTHON3_BIN = "python3"
Federico Ressia2aad942018-04-09 12:01:48 +020033
34
35def get_receiver_script(group, port, hello_message, ack_message, result_file):
36
37 return """
38import socket
39import struct
40import sys
41
42multicast_group = '%(group)s'
43server_address = ('', %(port)s)
44
45# Create the socket
46sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
47
48# Bind to the server address
49sock.bind(server_address)
50
51# Tell the operating system to add the socket to the multicast group
52# on all interfaces.
53group = socket.inet_aton(multicast_group)
54mreq = struct.pack('4sL', group, socket.INADDR_ANY)
55sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
56
57# Receive/respond loop
58with open('%(result_file)s', 'w') as f:
59 f.write('%(hello_message)s')
60 f.flush()
61 data, address = sock.recvfrom(1024)
62 f.write('received ' + str(len(data)) + ' bytes from ' + str(address))
63 f.write(str(data))
64sock.sendto(b'%(ack_message)s', address)
65 """ % {'group': group,
66 'port': port,
67 'hello_message': hello_message,
68 'ack_message': ack_message,
69 'result_file': result_file}
70
71
72def get_sender_script(group, port, message, result_file):
73
74 return """
75import socket
76import sys
77
78message = b'%(message)s'
79multicast_group = ('%(group)s', %(port)s)
80
81# Create the datagram socket
82sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
83# Set the time-to-live for messages to 1 so they do not go past the
84# local network segment.
85sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 1)
86
87# Set a timeout so the socket does not block indefinitely when trying
88# to receive data.
89sock.settimeout(1)
90
91with open('%(result_file)s', 'w') as f:
92 try:
93 # Send data to the multicast group
94 sent = sock.sendto(message, multicast_group)
95
96 # Look for responses from all recipients
97 while True:
98 try:
99 data, server = sock.recvfrom(1024)
100 except socket.timeout:
101 f.write('timed out, no more responses')
102 break
103 else:
104 f.write('received reply ' + str(data) + ' from ' + str(server))
105 finally:
106 sys.stdout.write('closing socket')
107 sock.close()
108 """ % {'group': group,
109 'port': port,
110 'message': message,
111 'result_file': result_file}
112
113
114class BaseMulticastTest(object):
115
116 credentials = ['primary']
117 force_tenant_isolation = False
118
119 # Import configuration options
120 available_type_drivers = (
121 CONF.neutron_plugin_options.available_type_drivers)
122
123 hello_message = "I am waiting..."
124 multicast_port = 5007
125 multicast_message = "Big Bang"
126 receiver_output_file = "/tmp/receiver_mcast_out"
127 sender_output_file = "/tmp/sender_mcast_out"
128
129 @classmethod
130 def skip_checks(cls):
131 super(BaseMulticastTest, cls).skip_checks()
132 advanced_image_available = (
133 CONF.neutron_plugin_options.advanced_image_ref or
134 CONF.neutron_plugin_options.default_image_is_advanced)
135 if not advanced_image_available:
136 skip_reason = "This test require advanced tools for this test"
137 raise cls.skipException(skip_reason)
138
139 @classmethod
140 def resource_setup(cls):
141 super(BaseMulticastTest, cls).resource_setup()
142
143 if CONF.neutron_plugin_options.default_image_is_advanced:
144 cls.flavor_ref = CONF.compute.flavor_ref
145 cls.image_ref = CONF.compute.image_ref
146 cls.username = CONF.validation.image_ssh_user
147 else:
148 cls.flavor_ref = (
149 CONF.neutron_plugin_options.advanced_image_flavor_ref)
150 cls.image_ref = CONF.neutron_plugin_options.advanced_image_ref
151 cls.username = CONF.neutron_plugin_options.advanced_image_ssh_user
152
153 # setup basic topology for servers we can log into it
154 cls.network = cls.create_network()
155 cls.subnet = cls.create_subnet(cls.network)
156 cls.router = cls.create_router_by_client()
157 cls.create_router_interface(cls.router['id'], cls.subnet['id'])
158
159 cls.keypair = cls.create_keypair()
160
161 cls.secgroup = cls.os_primary.network_client.create_security_group(
162 name='secgroup_mtu')
163 cls.security_groups.append(cls.secgroup['security_group'])
164 cls.create_loginable_secgroup_rule(
165 secgroup_id=cls.secgroup['security_group']['id'])
166 cls.create_pingable_secgroup_rule(
167 secgroup_id=cls.secgroup['security_group']['id'])
168 # Create security group rule for UDP (multicast traffic)
169 cls.create_secgroup_rules(
170 rule_list=[dict(protocol=constants.PROTO_NAME_UDP,
171 direction=constants.INGRESS_DIRECTION,
172 remote_ip_prefix=cls.any_addresses,
173 ethertype=cls.ethertype)],
174 secgroup_id=cls.secgroup['security_group']['id'])
175
176 # Multicast IP range to be used for multicast group IP asignement
177 if '-' in cls.multicast_group_range:
178 multicast_group_range = netaddr.IPRange(
179 *cls.multicast_group_range.split('-'))
180 else:
181 multicast_group_range = netaddr.IPNetwork(
182 cls.multicast_group_range)
183 cls.multicast_group_iter = iter(multicast_group_range)
184
185 def _create_server(self):
186 name = data_utils.rand_name("multicast-server")
187 server = self.create_server(
188 flavor_ref=self.flavor_ref,
189 image_ref=self.image_ref,
190 key_name=self.keypair['name'], name=name,
191 networks=[{'uuid': self.network['id']}],
192 security_groups=[{'name': self.secgroup['security_group']['name']}]
193 )['server']
194 self.wait_for_server_active(server)
195 port = self.client.list_ports(
196 network_id=self.network['id'], device_id=server['id'])['ports'][0]
197 server['fip'] = self.create_floatingip(port=port)
Slawek Kaplonski2f467ca2019-09-05 16:28:09 +0200198 server['ssh_client'] = ssh.Client(server['fip']['floating_ip_address'],
199 self.username,
200 pkey=self.keypair['private_key'])
201 self._check_python_installed_on_server(server['ssh_client'],
202 server['id'])
Federico Ressia2aad942018-04-09 12:01:48 +0200203 return server
204
Slawek Kaplonski2f467ca2019-09-05 16:28:09 +0200205 def _check_python_installed_on_server(self, ssh_client, server_id):
206 try:
207 ssh_client.execute_script('which %s' % PYTHON3_BIN)
208 except exceptions.SSHScriptFailed:
209 raise self.skipException(
210 "%s is not available on server %s" % (PYTHON3_BIN, server_id))
211
Federico Ressia2aad942018-04-09 12:01:48 +0200212 def _prepare_sender(self, server, mcast_address):
213 check_script = get_sender_script(
214 group=mcast_address, port=self.multicast_port,
215 message=self.multicast_message,
216 result_file=self.sender_output_file)
Slawek Kaplonski2f467ca2019-09-05 16:28:09 +0200217 server['ssh_client'].execute_script(
Federico Ressia2aad942018-04-09 12:01:48 +0200218 'echo "%s" > ~/multicast_traffic_sender.py' % check_script)
Federico Ressia2aad942018-04-09 12:01:48 +0200219
220 def _prepare_receiver(self, server, mcast_address):
221 check_script = get_receiver_script(
222 group=mcast_address, port=self.multicast_port,
223 hello_message=self.hello_message, ack_message=server['id'],
224 result_file=self.receiver_output_file)
225 ssh_client = ssh.Client(
226 server['fip']['floating_ip_address'],
227 self.username,
228 pkey=self.keypair['private_key'])
Slawek Kaplonski2f467ca2019-09-05 16:28:09 +0200229 self._check_python_installed_on_server(ssh_client, server['id'])
230 server['ssh_client'].execute_script(
Federico Ressia2aad942018-04-09 12:01:48 +0200231 'echo "%s" > ~/multicast_traffic_receiver.py' % check_script)
Federico Ressia2aad942018-04-09 12:01:48 +0200232
Rodolfo Alonso Hernandez4dea8062020-01-16 16:32:59 +0000233 @test.unstable_test("bug 1850288")
Federico Ressia2aad942018-04-09 12:01:48 +0200234 @decorators.idempotent_id('113486fc-24c9-4be4-8361-03b1c9892867')
235 def test_multicast_between_vms_on_same_network(self):
236 """Test multicast messaging between two servers on the same network
237
238 [Sender server] -> (Multicast network) -> [Receiver server]
239 """
240 sender = self._create_server()
241 receivers = [self._create_server() for _ in range(1)]
242 # Sender can be also receiver of multicast traffic
243 receivers.append(sender)
244 self._check_multicast_conectivity(sender=sender, receivers=receivers)
245
246 def _check_multicast_conectivity(self, sender, receivers):
247 """Test multi-cast messaging between two servers
248
249 [Sender server] -> ... some network topology ... -> [Receiver server]
250 """
251 mcast_address = next(self.multicast_group_iter)
252 LOG.debug("Multicast group address: %s", mcast_address)
253
254 def _message_received(client, msg, file_path):
255 result = client.execute_script(
256 "cat {path} || echo '{path} not exists yet'".format(
257 path=file_path))
258 return msg in result
259
Slawek Kaplonski2f467ca2019-09-05 16:28:09 +0200260 self._prepare_sender(sender, mcast_address)
Federico Ressia2aad942018-04-09 12:01:48 +0200261 receiver_ids = []
262 for receiver in receivers:
Slawek Kaplonski2f467ca2019-09-05 16:28:09 +0200263 self._prepare_receiver(receiver, mcast_address)
264 receiver['ssh_client'].execute_script(
265 "%s ~/multicast_traffic_receiver.py &" % PYTHON3_BIN,
266 shell="bash")
Federico Ressia2aad942018-04-09 12:01:48 +0200267 utils.wait_until_true(
268 lambda: _message_received(
Slawek Kaplonski2f467ca2019-09-05 16:28:09 +0200269 receiver['ssh_client'], self.hello_message,
Federico Ressia2aad942018-04-09 12:01:48 +0200270 self.receiver_output_file),
271 exception=RuntimeError(
272 "Receiver script didn't start properly on server "
273 "{!r}.".format(receiver['id'])))
274
Federico Ressia2aad942018-04-09 12:01:48 +0200275 receiver_ids.append(receiver['id'])
276
277 # Now lets run scripts on sender
Slawek Kaplonski2f467ca2019-09-05 16:28:09 +0200278 sender['ssh_client'].execute_script(
279 "%s ~/multicast_traffic_sender.py" % PYTHON3_BIN)
Federico Ressia2aad942018-04-09 12:01:48 +0200280
281 # And check if message was received
Slawek Kaplonski2f467ca2019-09-05 16:28:09 +0200282 for receiver in receivers:
Federico Ressia2aad942018-04-09 12:01:48 +0200283 utils.wait_until_true(
284 lambda: _message_received(
Slawek Kaplonski2f467ca2019-09-05 16:28:09 +0200285 receiver['ssh_client'], self.multicast_message,
Federico Ressia2aad942018-04-09 12:01:48 +0200286 self.receiver_output_file),
287 exception=RuntimeError(
288 "Receiver {!r} didn't get multicast message".format(
289 receiver['id'])))
290
291 # TODO(slaweq): add validation of answears on sended server
Slawek Kaplonski2f467ca2019-09-05 16:28:09 +0200292 replies_result = sender['ssh_client'].execute_script(
Federico Ressia2aad942018-04-09 12:01:48 +0200293 "cat {path} || echo '{path} not exists yet'".format(
294 path=self.sender_output_file))
295 for receiver_id in receiver_ids:
296 self.assertIn(receiver_id, replies_result)
297
298
299class MulticastTestIPv4(BaseMulticastTest, base.BaseTempestTestCase):
300
301 # Import configuration options
302 multicast_group_range = CONF.neutron_plugin_options.multicast_group_range
303
304 # IP version specific parameters
305 _ip_version = constants.IP_VERSION_4
306 any_addresses = constants.IPv4_ANY