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