blob: 74be21673fd013dac4631fa0dce6648c253483e6 [file] [log] [blame]
Itzik Brown1ef813a2016-06-06 12:56:21 +00001# Copyright 2016 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.
15import errno
16import socket
17import time
18
Chandan Kumarc125fd12017-11-15 19:41:01 +053019from neutron_lib.services.qos import constants as qos_consts
Itzik Brown1ef813a2016-06-06 12:56:21 +000020from oslo_log import log as logging
Chandan Kumarc125fd12017-11-15 19:41:01 +053021from tempest.common import utils as tutils
nfridman55f2ee62019-12-17 03:06:12 -050022from tempest.common import waiters
zahlabut7ebb66e2021-09-01 22:39:49 +030023from tempest.lib.common.utils import test_utils
Sławek Kapłońskic0caa2e2017-02-25 10:11:32 +000024from tempest.lib import decorators
Itzik Brown1ef813a2016-06-06 12:56:21 +000025
Chandan Kumar667d3d32017-09-22 12:24:06 +053026from neutron_tempest_plugin.api import base as base_api
Chandan Kumar667d3d32017-09-22 12:24:06 +053027from neutron_tempest_plugin.common import ssh
28from neutron_tempest_plugin.common import utils
29from neutron_tempest_plugin import config
30from neutron_tempest_plugin.scenario import base
31from neutron_tempest_plugin.scenario import constants
32from neutron_tempest_plugin.scenario import exceptions as sc_exceptions
Itzik Brown1ef813a2016-06-06 12:56:21 +000033
34CONF = config.CONF
35LOG = logging.getLogger(__name__)
36
37
Rodolfo Alonso Hernandezfa5ebc42019-07-26 17:02:40 +000038def _try_connect(host_ip, port, socket_timeout):
Itzik Brown1ef813a2016-06-06 12:56:21 +000039 try:
40 client_socket = socket.socket(socket.AF_INET,
41 socket.SOCK_STREAM)
42 client_socket.connect((host_ip, port))
Rodolfo Alonso Hernandezfa5ebc42019-07-26 17:02:40 +000043 client_socket.settimeout(socket_timeout)
Itzik Brown1ef813a2016-06-06 12:56:21 +000044 return client_socket
45 except socket.error as serr:
46 if serr.errno == errno.ECONNREFUSED:
47 raise sc_exceptions.SocketConnectionRefused(host=host_ip,
48 port=port)
49 else:
50 raise
51
52
Rodolfo Alonso Hernandezfa5ebc42019-07-26 17:02:40 +000053def _connect_socket(host, port, socket_timeout):
Brian Haleyaee61ac2018-10-09 20:00:27 -040054 """Try to initiate a connection to a host using an ip address and a port.
Itzik Brown1ef813a2016-06-06 12:56:21 +000055
56 Trying couple of times until a timeout is reached in case the listening
57 host is not ready yet.
58 """
59
60 start = time.time()
61 while True:
62 try:
Rodolfo Alonso Hernandezfa5ebc42019-07-26 17:02:40 +000063 return _try_connect(host, port, socket_timeout)
Itzik Brown1ef813a2016-06-06 12:56:21 +000064 except sc_exceptions.SocketConnectionRefused:
65 if time.time() - start > constants.SOCKET_CONNECT_TIMEOUT:
66 raise sc_exceptions.ConnectionTimeoutException(host=host,
67 port=port)
68
69
YAMAMOTO Takashia2cc2e52018-07-31 18:54:02 +090070class QoSTestMixin(object):
Itzik Brown1ef813a2016-06-06 12:56:21 +000071 credentials = ['primary', 'admin']
72 force_tenant_isolation = False
73
Itzik Brown1ef813a2016-06-06 12:56:21 +000074 TOLERANCE_FACTOR = 1.5
Rodolfo Alonso Hernandezfa5ebc42019-07-26 17:02:40 +000075 BUFFER_SIZE = 512
Brian Haley33ef4602018-04-26 14:37:49 -040076 LIMIT_BYTES_SEC = (constants.LIMIT_KILO_BITS_PER_SECOND * 1024 *
77 TOLERANCE_FACTOR / 8.0)
LIU Yulong5ba88ef2017-12-22 10:50:15 +080078 NC_PORT = 1234
Maciej Józefczyk41b80192020-03-03 17:10:57 +010079 DOWNLOAD_DURATION = 5
80 # NOTE(mjozefcz): This makes around 10 retries.
81 CHECK_TIMEOUT = DOWNLOAD_DURATION * 10
Itzik Brown1ef813a2016-06-06 12:56:21 +000082
Rodolfo Alonso Hernandezfa5ebc42019-07-26 17:02:40 +000083 def _check_bw(self, ssh_client, host, port, expected_bw=LIMIT_BYTES_SEC):
Maciej Józefczyk328edc82019-09-16 14:05:48 +000084 utils.kill_nc_process(ssh_client)
Slawek Kaplonskifd4141f2020-03-14 14:34:00 +010085 self.ensure_nc_listen(ssh_client, port, "tcp")
Miguel Angel Ajod47e21a2017-02-07 16:21:16 +010086
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +030087 # Open TCP socket to remote VM and download big file
Miguel Angel Ajod47e21a2017-02-07 16:21:16 +010088 start_time = time.time()
Maciej Józefczyk41b80192020-03-03 17:10:57 +010089 client_socket = _connect_socket(
90 host, port, constants.SOCKET_CONNECT_TIMEOUT)
Miguel Angel Ajod47e21a2017-02-07 16:21:16 +010091 total_bytes_read = 0
Rodolfo Alonso Hernandezfa5ebc42019-07-26 17:02:40 +000092 try:
Maciej Józefczyk41b80192020-03-03 17:10:57 +010093 while time.time() - start_time < self.DOWNLOAD_DURATION:
Rodolfo Alonso Hernandezfa5ebc42019-07-26 17:02:40 +000094 data = client_socket.recv(self.BUFFER_SIZE)
95 total_bytes_read += len(data)
Itzik Brown1ef813a2016-06-06 12:56:21 +000096
Rodolfo Alonso Hernandezfa5ebc42019-07-26 17:02:40 +000097 # Calculate and return actual BW + logging result
98 time_elapsed = time.time() - start_time
99 bytes_per_second = total_bytes_read / time_elapsed
Miguel Angel Ajod47e21a2017-02-07 16:21:16 +0100100
Rodolfo Alonso Hernandezfa5ebc42019-07-26 17:02:40 +0000101 LOG.debug("time_elapsed = %(time_elapsed).16f, "
102 "total_bytes_read = %(total_bytes_read)d, "
Maciej Józefczyk41b80192020-03-03 17:10:57 +0100103 "bytes_per_second = %(bytes_per_second)d, "
104 "expected_bw = %(expected_bw)d.",
Rodolfo Alonso Hernandezfa5ebc42019-07-26 17:02:40 +0000105 {'time_elapsed': time_elapsed,
106 'total_bytes_read': total_bytes_read,
Maciej Józefczyk41b80192020-03-03 17:10:57 +0100107 'bytes_per_second': bytes_per_second,
108 'expected_bw': expected_bw})
Rodolfo Alonso Hernandezfa5ebc42019-07-26 17:02:40 +0000109 return bytes_per_second <= expected_bw
110 except socket.timeout:
111 LOG.warning('Socket timeout while reading the remote file, bytes '
112 'read: %s', total_bytes_read)
Maciej Józefczyk328edc82019-09-16 14:05:48 +0000113 utils.kill_nc_process(ssh_client)
Rodolfo Alonso Hernandezfa5ebc42019-07-26 17:02:40 +0000114 return False
115 finally:
116 client_socket.close()
Itzik Brown1ef813a2016-06-06 12:56:21 +0000117
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800118 def _create_ssh_client(self):
119 return ssh.Client(self.fip['floating_ip_address'],
120 CONF.validation.image_ssh_user,
121 pkey=self.keypair['private_key'])
122
123 def _test_basic_resources(self):
124 self.setup_network_and_server()
125 self.check_connectivity(self.fip['floating_ip_address'],
126 CONF.validation.image_ssh_user,
127 self.keypair['private_key'])
128 rulesets = [{'protocol': 'tcp',
129 'direction': 'ingress',
130 'port_range_min': self.NC_PORT,
131 'port_range_max': self.NC_PORT,
132 'remote_ip_prefix': '0.0.0.0/0'}]
133 self.create_secgroup_rules(rulesets,
134 self.security_groups[-1]['id'])
135
136 def _create_qos_policy(self):
137 policy = self.os_admin.network_client.create_qos_policy(
138 name='test-policy',
139 description='test-qos-policy',
140 shared=True)
Eduardo Olivares98772912021-02-19 12:36:21 +0100141 self.qos_policies.append(policy['policy'])
zahlabut7ebb66e2021-09-01 22:39:49 +0300142 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
143 self.os_admin.network_client.delete_qos_policy, policy)
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800144 return policy['policy']['id']
145
zahlabut7ebb66e2021-09-01 22:39:49 +0300146 def _create_qos_bw_limit_rule(self, policy_id, rule_data):
147 rule = self.qos_bw_limit_rule_client.create_limit_bandwidth_rule(
148 qos_policy_id=policy_id,
149 **rule_data)['bandwidth_limit_rule']
150 self.addCleanup(
151 test_utils.call_and_ignore_notfound_exc,
152 self.qos_bw_limit_rule_client.delete_limit_bandwidth_rule,
153 policy_id, rule['id'])
154 return rule
155
nfridman55f2ee62019-12-17 03:06:12 -0500156 def _create_server_by_port(self, port=None):
157 """Launch an instance using a port interface;
158
159 In case that the given port is None, a new port is created,
160 activated and configured with inbound SSH and TCP connection.
161 """
162 # Create and activate the port that will be assign to the instance.
163 if port is None:
164 secgroup = self.create_security_group()
165 self.create_loginable_secgroup_rule(
166 secgroup_id=secgroup['id'])
167
168 secgroup_rules = [{'protocol': 'tcp',
169 'direction': 'ingress',
170 'port_range_min': self.NC_PORT,
171 'port_range_max': self.NC_PORT,
172 'remote_ip_prefix': '0.0.0.0/0'}]
173
174 self.create_secgroup_rules(secgroup_rules,
175 secgroup['id'])
176
177 port = self.create_port(self.network,
178 security_groups=[secgroup['id']])
179 self.fip = self.create_floatingip(port=port)
180
181 keypair = self.create_keypair()
182
183 server_kwargs = {
184 'flavor_ref': CONF.compute.flavor_ref,
185 'image_ref': CONF.compute.image_ref,
186 'key_name': keypair['name'],
187 'networks': [{'port': port['id']}],
188 }
189
190 server = self.create_server(**server_kwargs)
191 self.wait_for_server_active(server['server'])
Slawek Kaplonski2211eab2020-10-20 16:43:53 +0200192 self.wait_for_guest_os_ready(server['server'])
nfridman55f2ee62019-12-17 03:06:12 -0500193 self.check_connectivity(self.fip['floating_ip_address'],
194 CONF.validation.image_ssh_user,
195 keypair['private_key'])
196 return server, port
197
YAMAMOTO Takashia2cc2e52018-07-31 18:54:02 +0900198
199class QoSTest(QoSTestMixin, base.BaseTempestTestCase):
200 @classmethod
201 @tutils.requires_ext(extension="qos", service="network")
202 @base_api.require_qos_rule_type(qos_consts.RULE_TYPE_BANDWIDTH_LIMIT)
203 def resource_setup(cls):
204 super(QoSTest, cls).resource_setup()
205
Eduardo Olivares98772912021-02-19 12:36:21 +0100206 @classmethod
207 def setup_clients(cls):
208 super(QoSTest, cls).setup_clients()
209 cls.admin_client = cls.os_admin.network_client
zahlabut7ebb66e2021-09-01 22:39:49 +0300210 cls.qos_bw_limit_rule_client = \
211 cls.os_admin.qos_limit_bandwidth_rules_client
Eduardo Olivares98772912021-02-19 12:36:21 +0100212
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300213 @decorators.idempotent_id('00682a0c-b72e-11e8-b81e-8c16450ea513')
214 def test_qos_basic_and_update(self):
Arkady Shtemplerc2b80702020-10-10 15:15:43 +0300215 """This test covers following scenarios:
Itzik Brown1ef813a2016-06-06 12:56:21 +0000216
Arkady Shtemplerc2b80702020-10-10 15:15:43 +0300217 1) Create a QoS policy associated with the network.
218 Expected result: BW is limited according the values set in
219 QoS policy rule.
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300220
Arkady Shtemplerc2b80702020-10-10 15:15:43 +0300221 2) Update QoS policy associated with the network.
222 Expected result: BW is limited according the new values
223 set in QoS policy rule.
224
225 3) Create a new QoS policy associated with the VM port.
226 Expected result: BW is limited according the values set in
227 new QoS policy rule.
228 Note: Neutron port is prioritized higher than Network, means
229 that: "Neutron Port Priority" is also covered.
230
231 4) Update QoS policy associated with the VM port.
232 Expected result: BW is limited according the new values set
233 in QoS policy rule.
234
Itzik Brown1ef813a2016-06-06 12:56:21 +0000235 """
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300236
237 # Setup resources
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800238 self._test_basic_resources()
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800239 ssh_client = self._create_ssh_client()
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300240
241 # Create QoS policy
242 bw_limit_policy_id = self._create_qos_policy()
243
244 # As admin user create QoS rule
zahlabut7ebb66e2021-09-01 22:39:49 +0300245 rule_data = {
246 'max_kbps': constants.LIMIT_KILO_BITS_PER_SECOND,
247 'max_burst_kbps': constants.LIMIT_KILO_BITS_PER_SECOND}
248 rule_id = self._create_qos_bw_limit_rule(
249 bw_limit_policy_id, rule_data)['id']
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300250
251 # Associate QoS to the network
252 self.os_admin.network_client.update_network(
253 self.network['id'], qos_policy_id=bw_limit_policy_id)
254
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300255 # Basic test, Check that actual BW while downloading file
256 # is as expected (Original BW)
Itzik Brown1ef813a2016-06-06 12:56:21 +0000257 utils.wait_until_true(lambda: self._check_bw(
258 ssh_client,
259 self.fip['floating_ip_address'],
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800260 port=self.NC_PORT),
Maciej Józefczyk41b80192020-03-03 17:10:57 +0100261 timeout=self.CHECK_TIMEOUT,
Arkady Shtemplerc2b80702020-10-10 15:15:43 +0300262 sleep=1,
263 exception=RuntimeError(
264 'Failed scenario: "Create a QoS policy associated with'
265 ' the network" Actual BW is not as expected!'))
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300266
267 # As admin user update QoS rule
zahlabut7ebb66e2021-09-01 22:39:49 +0300268 rule_update_data = {
269 'max_kbps': constants.LIMIT_KILO_BITS_PER_SECOND * 2,
270 'max_burst_kbps': constants.LIMIT_KILO_BITS_PER_SECOND * 2}
271 self.qos_bw_limit_rule_client.update_limit_bandwidth_rule(
272 qos_policy_id=bw_limit_policy_id, rule_id=rule_id,
273 **rule_update_data)
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300274
275 # Check that actual BW while downloading file
276 # is as expected (Update BW)
277 utils.wait_until_true(lambda: self._check_bw(
278 ssh_client,
279 self.fip['floating_ip_address'],
280 port=self.NC_PORT,
281 expected_bw=QoSTest.LIMIT_BYTES_SEC * 2),
Maciej Józefczyk41b80192020-03-03 17:10:57 +0100282 timeout=self.CHECK_TIMEOUT,
Arkady Shtemplerc2b80702020-10-10 15:15:43 +0300283 sleep=1,
284 exception=RuntimeError(
285 'Failed scenario: "Update QoS policy associated with'
286 ' the network" Actual BW is not as expected!'))
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300287
288 # Create a new QoS policy
289 bw_limit_policy_id_new = self._create_qos_policy()
290
291 # As admin user create a new QoS rule
zahlabut7ebb66e2021-09-01 22:39:49 +0300292 rule_data_new = {
293 'max_kbps': constants.LIMIT_KILO_BITS_PER_SECOND,
294 'max_burst_kbps': constants.LIMIT_KILO_BITS_PER_SECOND}
295 rule_id_new = self._create_qos_bw_limit_rule(
296 bw_limit_policy_id_new, rule_data_new)['id']
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300297
298 # Associate a new QoS policy to Neutron port
299 self.os_admin.network_client.update_port(
300 self.port['id'], qos_policy_id=bw_limit_policy_id_new)
301
302 # Check that actual BW while downloading file
303 # is as expected (Original BW)
304 utils.wait_until_true(lambda: self._check_bw(
305 ssh_client,
306 self.fip['floating_ip_address'],
307 port=self.NC_PORT),
Maciej Józefczyk41b80192020-03-03 17:10:57 +0100308 timeout=self.CHECK_TIMEOUT,
Arkady Shtemplerc2b80702020-10-10 15:15:43 +0300309 sleep=1,
310 exception=RuntimeError(
311 'Failed scenario: "Create a new QoS policy associated with'
312 ' the VM port" Actual BW is not as expected!'))
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300313
314 # As admin user update QoS rule
zahlabut7ebb66e2021-09-01 22:39:49 +0300315 rule_update_data = {
316 'max_kbps': constants.LIMIT_KILO_BITS_PER_SECOND * 3,
317 'max_burst_kbps': constants.LIMIT_KILO_BITS_PER_SECOND * 3}
318 self.qos_bw_limit_rule_client.update_limit_bandwidth_rule(
319 qos_policy_id=bw_limit_policy_id_new, rule_id=rule_id_new,
320 **rule_update_data)
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300321
322 # Check that actual BW while downloading file
323 # is as expected (Update BW)
324 utils.wait_until_true(lambda: self._check_bw(
325 ssh_client,
326 self.fip['floating_ip_address'],
Maciej Józefczyk41b80192020-03-03 17:10:57 +0100327 port=self.NC_PORT,
328 expected_bw=QoSTest.LIMIT_BYTES_SEC * 3),
329 timeout=self.CHECK_TIMEOUT,
Arkady Shtemplerc2b80702020-10-10 15:15:43 +0300330 sleep=1,
331 exception=RuntimeError(
332 'Failed scenario: "Update QoS policy associated with'
333 ' the VM port" Actual BW is not as expected!'))
nfridman55f2ee62019-12-17 03:06:12 -0500334
335 @decorators.idempotent_id('66e5673e-0522-11ea-8d71-362b9e155667')
336 def test_attach_previously_used_port_to_new_instance(self):
337 """The test spawns new instance using port with QoS policy.
338
339        Ports with attached QoS policy could be used multiple times.
340        The policy rules have to be enforced on the new machines.
341 """
342 self.network = self.create_network()
343 self.subnet = self.create_subnet(self.network)
344 self.router = self.create_router_by_client()
345 self.create_router_interface(self.router['id'], self.subnet['id'])
346
347 vm, vm_port = self._create_server_by_port()
348
349 port_policy = self.os_admin.network_client.create_qos_policy(
350 name='port-policy',
351 description='policy for attach',
352 shared=False)['policy']
353
zahlabut7ebb66e2021-09-01 22:39:49 +0300354 rule_data = {
355 'max_kbps': constants.LIMIT_KILO_BITS_PER_SECOND,
356 'max_burst_kbps': constants.LIMIT_KILO_BITS_PER_SECOND}
357 rule = self._create_qos_bw_limit_rule(port_policy['id'], rule_data)
nfridman55f2ee62019-12-17 03:06:12 -0500358
359 self.os_admin.network_client.update_port(
360 vm_port['id'], qos_policy_id=port_policy['id'])
361
362 self.os_primary.servers_client.delete_server(vm['server']['id'])
363 waiters.wait_for_server_termination(
364 self.os_primary.servers_client,
365 vm['server']['id'])
366
367 # Launch a new server using the same port with attached policy
368 self._create_server_by_port(port=vm_port)
369
370 retrieved_port = self.os_admin.network_client.show_port(
371 vm_port['id'])
372 self.assertEqual(port_policy['id'],
373 retrieved_port['port']['qos_policy_id'],
374 """The expected policy ID is {0},
375 the actual value is {1}""".
376 format(port_policy['id'],
377 retrieved_port['port']['qos_policy_id']))
378
379 retrieved_policy = self.os_admin.network_client.show_qos_policy(
380 retrieved_port['port']['qos_policy_id'])
381
382 retrieved_rule_id = retrieved_policy['policy']['rules'][0]['id']
383 self.assertEqual(rule['id'],
384 retrieved_rule_id,
385 """The expected rule ID is {0},
386 the actual value is {1}""".
387 format(rule['id'], retrieved_rule_id))
nfridman5451fab2020-03-12 11:17:43 +0200388
389 @decorators.idempotent_id('4eee64da-5646-11ea-82b4-0242ac130003')
390 def test_create_instance_using_network_with_existing_policy(self):
391 network = self.create_network()
392
393 qos_policy = self.os_admin.network_client.create_qos_policy(
394 name='network-policy',
395 shared=False)['policy']
396
zahlabut7ebb66e2021-09-01 22:39:49 +0300397 rule_data = {
398 'max_kbps': constants.LIMIT_KILO_BITS_PER_SECOND,
399 'max_burst_kbps': constants.LIMIT_KILO_BITS_PER_SECOND}
400 rule = self._create_qos_bw_limit_rule(qos_policy['id'], rule_data)
nfridman5451fab2020-03-12 11:17:43 +0200401
402 network = self.os_admin.network_client.update_network(
403 network['id'],
404 qos_policy_id=qos_policy['id'])['network']
405 self.setup_network_and_server(network=network)
406 retrieved_net = self.client.show_network(network['id'])
407 self.assertEqual(qos_policy['id'],
408 retrieved_net['network']['qos_policy_id'],
409 """The expected policy ID is {0},
410 the actual value is {1}""".
411 format(qos_policy['id'],
412 retrieved_net['network']['qos_policy_id']))
413
414 retrieved_policy = self.os_admin.network_client.show_qos_policy(
415 retrieved_net['network']['qos_policy_id'])
416 retrieved_rule_id = retrieved_policy['policy']['rules'][0]['id']
417
zahlabut7ebb66e2021-09-01 22:39:49 +0300418 self.assertEqual(rule['id'],
nfridman5451fab2020-03-12 11:17:43 +0200419 retrieved_rule_id,
420 """The expected rule ID is {0},
421 the actual value is {1}""".
zahlabut7ebb66e2021-09-01 22:39:49 +0300422 format(rule['id'],
nfridman5451fab2020-03-12 11:17:43 +0200423 retrieved_rule_id))