blob: 9b7958249e52613b6ae2511186a5302fed6efedb [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
18from oslo_log import log
19from tempest.lib.common.utils import data_utils
20from tempest.lib import decorators
21
22from neutron_tempest_plugin.common import ssh
23from neutron_tempest_plugin.common import utils
24from neutron_tempest_plugin import config
Slawek Kaplonski2f467ca2019-09-05 16:28:09 +020025from neutron_tempest_plugin import exceptions
Federico Ressia2aad942018-04-09 12:01:48 +020026from neutron_tempest_plugin.scenario import base
27
28
29CONF = config.CONF
30LOG = log.getLogger(__name__)
Slawek Kaplonski2f467ca2019-09-05 16:28:09 +020031PYTHON3_BIN = "python3"
Federico Ressia2aad942018-04-09 12:01:48 +020032
33
34def get_receiver_script(group, port, hello_message, ack_message, result_file):
35
36 return """
37import socket
38import struct
39import sys
40
41multicast_group = '%(group)s'
42server_address = ('', %(port)s)
43
44# Create the socket
45sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
46
47# Bind to the server address
48sock.bind(server_address)
49
50# Tell the operating system to add the socket to the multicast group
51# on all interfaces.
52group = socket.inet_aton(multicast_group)
53mreq = struct.pack('4sL', group, socket.INADDR_ANY)
54sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
55
56# Receive/respond loop
57with open('%(result_file)s', 'w') as f:
58 f.write('%(hello_message)s')
59 f.flush()
60 data, address = sock.recvfrom(1024)
61 f.write('received ' + str(len(data)) + ' bytes from ' + str(address))
62 f.write(str(data))
63sock.sendto(b'%(ack_message)s', address)
64 """ % {'group': group,
65 'port': port,
66 'hello_message': hello_message,
67 'ack_message': ack_message,
68 'result_file': result_file}
69
70
71def get_sender_script(group, port, message, result_file):
72
73 return """
74import socket
75import sys
76
77message = b'%(message)s'
78multicast_group = ('%(group)s', %(port)s)
79
80# Create the datagram socket
81sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
82# Set the time-to-live for messages to 1 so they do not go past the
83# local network segment.
84sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 1)
85
86# Set a timeout so the socket does not block indefinitely when trying
87# to receive data.
88sock.settimeout(1)
89
90with open('%(result_file)s', 'w') as f:
91 try:
92 # Send data to the multicast group
93 sent = sock.sendto(message, multicast_group)
94
95 # Look for responses from all recipients
96 while True:
97 try:
98 data, server = sock.recvfrom(1024)
99 except socket.timeout:
100 f.write('timed out, no more responses')
101 break
102 else:
103 f.write('received reply ' + str(data) + ' from ' + str(server))
104 finally:
105 sys.stdout.write('closing socket')
106 sock.close()
107 """ % {'group': group,
108 'port': port,
109 'message': message,
110 'result_file': result_file}
111
112
113class BaseMulticastTest(object):
114
115 credentials = ['primary']
116 force_tenant_isolation = False
117
118 # Import configuration options
119 available_type_drivers = (
120 CONF.neutron_plugin_options.available_type_drivers)
121
122 hello_message = "I am waiting..."
123 multicast_port = 5007
124 multicast_message = "Big Bang"
125 receiver_output_file = "/tmp/receiver_mcast_out"
126 sender_output_file = "/tmp/sender_mcast_out"
127
128 @classmethod
129 def skip_checks(cls):
130 super(BaseMulticastTest, cls).skip_checks()
131 advanced_image_available = (
132 CONF.neutron_plugin_options.advanced_image_ref or
133 CONF.neutron_plugin_options.default_image_is_advanced)
134 if not advanced_image_available:
135 skip_reason = "This test require advanced tools for this test"
136 raise cls.skipException(skip_reason)
137
138 @classmethod
139 def resource_setup(cls):
140 super(BaseMulticastTest, cls).resource_setup()
141
142 if CONF.neutron_plugin_options.default_image_is_advanced:
143 cls.flavor_ref = CONF.compute.flavor_ref
144 cls.image_ref = CONF.compute.image_ref
145 cls.username = CONF.validation.image_ssh_user
146 else:
147 cls.flavor_ref = (
148 CONF.neutron_plugin_options.advanced_image_flavor_ref)
149 cls.image_ref = CONF.neutron_plugin_options.advanced_image_ref
150 cls.username = CONF.neutron_plugin_options.advanced_image_ssh_user
151
152 # setup basic topology for servers we can log into it
153 cls.network = cls.create_network()
154 cls.subnet = cls.create_subnet(cls.network)
155 cls.router = cls.create_router_by_client()
156 cls.create_router_interface(cls.router['id'], cls.subnet['id'])
157
158 cls.keypair = cls.create_keypair()
159
160 cls.secgroup = cls.os_primary.network_client.create_security_group(
161 name='secgroup_mtu')
162 cls.security_groups.append(cls.secgroup['security_group'])
163 cls.create_loginable_secgroup_rule(
164 secgroup_id=cls.secgroup['security_group']['id'])
165 cls.create_pingable_secgroup_rule(
166 secgroup_id=cls.secgroup['security_group']['id'])
167 # Create security group rule for UDP (multicast traffic)
168 cls.create_secgroup_rules(
169 rule_list=[dict(protocol=constants.PROTO_NAME_UDP,
170 direction=constants.INGRESS_DIRECTION,
171 remote_ip_prefix=cls.any_addresses,
172 ethertype=cls.ethertype)],
173 secgroup_id=cls.secgroup['security_group']['id'])
174
175 # Multicast IP range to be used for multicast group IP asignement
176 if '-' in cls.multicast_group_range:
177 multicast_group_range = netaddr.IPRange(
178 *cls.multicast_group_range.split('-'))
179 else:
180 multicast_group_range = netaddr.IPNetwork(
181 cls.multicast_group_range)
182 cls.multicast_group_iter = iter(multicast_group_range)
183
184 def _create_server(self):
185 name = data_utils.rand_name("multicast-server")
186 server = self.create_server(
187 flavor_ref=self.flavor_ref,
188 image_ref=self.image_ref,
189 key_name=self.keypair['name'], name=name,
190 networks=[{'uuid': self.network['id']}],
191 security_groups=[{'name': self.secgroup['security_group']['name']}]
192 )['server']
193 self.wait_for_server_active(server)
194 port = self.client.list_ports(
195 network_id=self.network['id'], device_id=server['id'])['ports'][0]
196 server['fip'] = self.create_floatingip(port=port)
Slawek Kaplonski2f467ca2019-09-05 16:28:09 +0200197 server['ssh_client'] = ssh.Client(server['fip']['floating_ip_address'],
198 self.username,
199 pkey=self.keypair['private_key'])
200 self._check_python_installed_on_server(server['ssh_client'],
201 server['id'])
Federico Ressia2aad942018-04-09 12:01:48 +0200202 return server
203
Slawek Kaplonski2f467ca2019-09-05 16:28:09 +0200204 def _check_python_installed_on_server(self, ssh_client, server_id):
205 try:
206 ssh_client.execute_script('which %s' % PYTHON3_BIN)
207 except exceptions.SSHScriptFailed:
208 raise self.skipException(
209 "%s is not available on server %s" % (PYTHON3_BIN, server_id))
210
Federico Ressia2aad942018-04-09 12:01:48 +0200211 def _prepare_sender(self, server, mcast_address):
212 check_script = get_sender_script(
213 group=mcast_address, port=self.multicast_port,
214 message=self.multicast_message,
215 result_file=self.sender_output_file)
Slawek Kaplonski2f467ca2019-09-05 16:28:09 +0200216 server['ssh_client'].execute_script(
Federico Ressia2aad942018-04-09 12:01:48 +0200217 'echo "%s" > ~/multicast_traffic_sender.py' % check_script)
Federico Ressia2aad942018-04-09 12:01:48 +0200218
219 def _prepare_receiver(self, server, mcast_address):
220 check_script = get_receiver_script(
221 group=mcast_address, port=self.multicast_port,
222 hello_message=self.hello_message, ack_message=server['id'],
223 result_file=self.receiver_output_file)
224 ssh_client = ssh.Client(
225 server['fip']['floating_ip_address'],
226 self.username,
227 pkey=self.keypair['private_key'])
Slawek Kaplonski2f467ca2019-09-05 16:28:09 +0200228 self._check_python_installed_on_server(ssh_client, server['id'])
229 server['ssh_client'].execute_script(
Federico Ressia2aad942018-04-09 12:01:48 +0200230 'echo "%s" > ~/multicast_traffic_receiver.py' % check_script)
Federico Ressia2aad942018-04-09 12:01:48 +0200231
232 @decorators.idempotent_id('113486fc-24c9-4be4-8361-03b1c9892867')
233 def test_multicast_between_vms_on_same_network(self):
234 """Test multicast messaging between two servers on the same network
235
236 [Sender server] -> (Multicast network) -> [Receiver server]
237 """
238 sender = self._create_server()
239 receivers = [self._create_server() for _ in range(1)]
240 # Sender can be also receiver of multicast traffic
241 receivers.append(sender)
242 self._check_multicast_conectivity(sender=sender, receivers=receivers)
243
244 def _check_multicast_conectivity(self, sender, receivers):
245 """Test multi-cast messaging between two servers
246
247 [Sender server] -> ... some network topology ... -> [Receiver server]
248 """
249 mcast_address = next(self.multicast_group_iter)
250 LOG.debug("Multicast group address: %s", mcast_address)
251
252 def _message_received(client, msg, file_path):
253 result = client.execute_script(
254 "cat {path} || echo '{path} not exists yet'".format(
255 path=file_path))
256 return msg in result
257
Slawek Kaplonski2f467ca2019-09-05 16:28:09 +0200258 self._prepare_sender(sender, mcast_address)
Federico Ressia2aad942018-04-09 12:01:48 +0200259 receiver_ids = []
260 for receiver in receivers:
Slawek Kaplonski2f467ca2019-09-05 16:28:09 +0200261 self._prepare_receiver(receiver, mcast_address)
262 receiver['ssh_client'].execute_script(
263 "%s ~/multicast_traffic_receiver.py &" % PYTHON3_BIN,
264 shell="bash")
Federico Ressia2aad942018-04-09 12:01:48 +0200265 utils.wait_until_true(
266 lambda: _message_received(
Slawek Kaplonski2f467ca2019-09-05 16:28:09 +0200267 receiver['ssh_client'], self.hello_message,
Federico Ressia2aad942018-04-09 12:01:48 +0200268 self.receiver_output_file),
269 exception=RuntimeError(
270 "Receiver script didn't start properly on server "
271 "{!r}.".format(receiver['id'])))
272
Federico Ressia2aad942018-04-09 12:01:48 +0200273 receiver_ids.append(receiver['id'])
274
275 # Now lets run scripts on sender
Slawek Kaplonski2f467ca2019-09-05 16:28:09 +0200276 sender['ssh_client'].execute_script(
277 "%s ~/multicast_traffic_sender.py" % PYTHON3_BIN)
Federico Ressia2aad942018-04-09 12:01:48 +0200278
279 # And check if message was received
Slawek Kaplonski2f467ca2019-09-05 16:28:09 +0200280 for receiver in receivers:
Federico Ressia2aad942018-04-09 12:01:48 +0200281 utils.wait_until_true(
282 lambda: _message_received(
Slawek Kaplonski2f467ca2019-09-05 16:28:09 +0200283 receiver['ssh_client'], self.multicast_message,
Federico Ressia2aad942018-04-09 12:01:48 +0200284 self.receiver_output_file),
285 exception=RuntimeError(
286 "Receiver {!r} didn't get multicast message".format(
287 receiver['id'])))
288
289 # TODO(slaweq): add validation of answears on sended server
Slawek Kaplonski2f467ca2019-09-05 16:28:09 +0200290 replies_result = sender['ssh_client'].execute_script(
Federico Ressia2aad942018-04-09 12:01:48 +0200291 "cat {path} || echo '{path} not exists yet'".format(
292 path=self.sender_output_file))
293 for receiver_id in receiver_ids:
294 self.assertIn(receiver_id, replies_result)
295
296
297class MulticastTestIPv4(BaseMulticastTest, base.BaseTempestTestCase):
298
299 # Import configuration options
300 multicast_group_range = CONF.neutron_plugin_options.multicast_group_range
301
302 # IP version specific parameters
303 _ip_version = constants.IP_VERSION_4
304 any_addresses = constants.IPv4_ANY