blob: b0921c877f30cd12b578b89097fd35beec4fc3ee [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,
Anna Arhipova55840c22025-06-03 20:32:57 +0200143 self.os_admin.network_client.delete_qos_policy,
144 policy['policy']['id'])
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800145 return policy['policy']['id']
146
zahlabut7ebb66e2021-09-01 22:39:49 +0300147 def _create_qos_bw_limit_rule(self, policy_id, rule_data):
148 rule = self.qos_bw_limit_rule_client.create_limit_bandwidth_rule(
149 qos_policy_id=policy_id,
150 **rule_data)['bandwidth_limit_rule']
151 self.addCleanup(
152 test_utils.call_and_ignore_notfound_exc,
153 self.qos_bw_limit_rule_client.delete_limit_bandwidth_rule,
154 policy_id, rule['id'])
155 return rule
156
nfridman55f2ee62019-12-17 03:06:12 -0500157 def _create_server_by_port(self, port=None):
158 """Launch an instance using a port interface;
159
160 In case that the given port is None, a new port is created,
161 activated and configured with inbound SSH and TCP connection.
162 """
163 # Create and activate the port that will be assign to the instance.
164 if port is None:
165 secgroup = self.create_security_group()
166 self.create_loginable_secgroup_rule(
167 secgroup_id=secgroup['id'])
168
169 secgroup_rules = [{'protocol': 'tcp',
170 'direction': 'ingress',
171 'port_range_min': self.NC_PORT,
172 'port_range_max': self.NC_PORT,
173 'remote_ip_prefix': '0.0.0.0/0'}]
174
175 self.create_secgroup_rules(secgroup_rules,
176 secgroup['id'])
177
178 port = self.create_port(self.network,
179 security_groups=[secgroup['id']])
180 self.fip = self.create_floatingip(port=port)
181
182 keypair = self.create_keypair()
183
184 server_kwargs = {
185 'flavor_ref': CONF.compute.flavor_ref,
186 'image_ref': CONF.compute.image_ref,
187 'key_name': keypair['name'],
188 'networks': [{'port': port['id']}],
189 }
190
191 server = self.create_server(**server_kwargs)
192 self.wait_for_server_active(server['server'])
Slawek Kaplonski2211eab2020-10-20 16:43:53 +0200193 self.wait_for_guest_os_ready(server['server'])
nfridman55f2ee62019-12-17 03:06:12 -0500194 self.check_connectivity(self.fip['floating_ip_address'],
195 CONF.validation.image_ssh_user,
196 keypair['private_key'])
197 return server, port
198
YAMAMOTO Takashia2cc2e52018-07-31 18:54:02 +0900199
200class QoSTest(QoSTestMixin, base.BaseTempestTestCase):
201 @classmethod
202 @tutils.requires_ext(extension="qos", service="network")
203 @base_api.require_qos_rule_type(qos_consts.RULE_TYPE_BANDWIDTH_LIMIT)
204 def resource_setup(cls):
205 super(QoSTest, cls).resource_setup()
206
Eduardo Olivares98772912021-02-19 12:36:21 +0100207 @classmethod
208 def setup_clients(cls):
209 super(QoSTest, cls).setup_clients()
210 cls.admin_client = cls.os_admin.network_client
zahlabut7ebb66e2021-09-01 22:39:49 +0300211 cls.qos_bw_limit_rule_client = \
212 cls.os_admin.qos_limit_bandwidth_rules_client
Eduardo Olivares98772912021-02-19 12:36:21 +0100213
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300214 @decorators.idempotent_id('00682a0c-b72e-11e8-b81e-8c16450ea513')
215 def test_qos_basic_and_update(self):
Arkady Shtemplerc2b80702020-10-10 15:15:43 +0300216 """This test covers following scenarios:
Itzik Brown1ef813a2016-06-06 12:56:21 +0000217
Arkady Shtemplerc2b80702020-10-10 15:15:43 +0300218 1) Create a QoS policy associated with the network.
219 Expected result: BW is limited according the values set in
220 QoS policy rule.
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300221
Arkady Shtemplerc2b80702020-10-10 15:15:43 +0300222 2) Update QoS policy associated with the network.
223 Expected result: BW is limited according the new values
224 set in QoS policy rule.
225
226 3) Create a new QoS policy associated with the VM port.
227 Expected result: BW is limited according the values set in
228 new QoS policy rule.
229 Note: Neutron port is prioritized higher than Network, means
230 that: "Neutron Port Priority" is also covered.
231
232 4) Update QoS policy associated with the VM port.
233 Expected result: BW is limited according the new values set
234 in QoS policy rule.
235
Itzik Brown1ef813a2016-06-06 12:56:21 +0000236 """
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300237
238 # Setup resources
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800239 self._test_basic_resources()
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800240 ssh_client = self._create_ssh_client()
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300241
242 # Create QoS policy
243 bw_limit_policy_id = self._create_qos_policy()
244
245 # As admin user create QoS rule
zahlabut7ebb66e2021-09-01 22:39:49 +0300246 rule_data = {
247 'max_kbps': constants.LIMIT_KILO_BITS_PER_SECOND,
248 'max_burst_kbps': constants.LIMIT_KILO_BITS_PER_SECOND}
249 rule_id = self._create_qos_bw_limit_rule(
250 bw_limit_policy_id, rule_data)['id']
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300251
252 # Associate QoS to the network
253 self.os_admin.network_client.update_network(
254 self.network['id'], qos_policy_id=bw_limit_policy_id)
255
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300256 # Basic test, Check that actual BW while downloading file
257 # is as expected (Original BW)
Itzik Brown1ef813a2016-06-06 12:56:21 +0000258 utils.wait_until_true(lambda: self._check_bw(
259 ssh_client,
260 self.fip['floating_ip_address'],
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800261 port=self.NC_PORT),
Maciej Józefczyk41b80192020-03-03 17:10:57 +0100262 timeout=self.CHECK_TIMEOUT,
Arkady Shtemplerc2b80702020-10-10 15:15:43 +0300263 sleep=1,
264 exception=RuntimeError(
265 'Failed scenario: "Create a QoS policy associated with'
266 ' the network" Actual BW is not as expected!'))
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300267
268 # As admin user update QoS rule
zahlabut7ebb66e2021-09-01 22:39:49 +0300269 rule_update_data = {
270 'max_kbps': constants.LIMIT_KILO_BITS_PER_SECOND * 2,
271 'max_burst_kbps': constants.LIMIT_KILO_BITS_PER_SECOND * 2}
272 self.qos_bw_limit_rule_client.update_limit_bandwidth_rule(
273 qos_policy_id=bw_limit_policy_id, rule_id=rule_id,
274 **rule_update_data)
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300275
276 # Check that actual BW while downloading file
277 # is as expected (Update BW)
278 utils.wait_until_true(lambda: self._check_bw(
279 ssh_client,
280 self.fip['floating_ip_address'],
281 port=self.NC_PORT,
282 expected_bw=QoSTest.LIMIT_BYTES_SEC * 2),
Maciej Józefczyk41b80192020-03-03 17:10:57 +0100283 timeout=self.CHECK_TIMEOUT,
Arkady Shtemplerc2b80702020-10-10 15:15:43 +0300284 sleep=1,
285 exception=RuntimeError(
286 'Failed scenario: "Update QoS policy associated with'
287 ' the network" Actual BW is not as expected!'))
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300288
289 # Create a new QoS policy
290 bw_limit_policy_id_new = self._create_qos_policy()
291
292 # As admin user create a new QoS rule
zahlabut7ebb66e2021-09-01 22:39:49 +0300293 rule_data_new = {
294 'max_kbps': constants.LIMIT_KILO_BITS_PER_SECOND,
295 'max_burst_kbps': constants.LIMIT_KILO_BITS_PER_SECOND}
296 rule_id_new = self._create_qos_bw_limit_rule(
297 bw_limit_policy_id_new, rule_data_new)['id']
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300298
299 # Associate a new QoS policy to Neutron port
300 self.os_admin.network_client.update_port(
301 self.port['id'], qos_policy_id=bw_limit_policy_id_new)
302
303 # Check that actual BW while downloading file
304 # is as expected (Original BW)
305 utils.wait_until_true(lambda: self._check_bw(
306 ssh_client,
307 self.fip['floating_ip_address'],
308 port=self.NC_PORT),
Maciej Józefczyk41b80192020-03-03 17:10:57 +0100309 timeout=self.CHECK_TIMEOUT,
Arkady Shtemplerc2b80702020-10-10 15:15:43 +0300310 sleep=1,
311 exception=RuntimeError(
312 'Failed scenario: "Create a new QoS policy associated with'
313 ' the VM port" Actual BW is not as expected!'))
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300314
315 # As admin user update QoS rule
zahlabut7ebb66e2021-09-01 22:39:49 +0300316 rule_update_data = {
317 'max_kbps': constants.LIMIT_KILO_BITS_PER_SECOND * 3,
318 'max_burst_kbps': constants.LIMIT_KILO_BITS_PER_SECOND * 3}
319 self.qos_bw_limit_rule_client.update_limit_bandwidth_rule(
320 qos_policy_id=bw_limit_policy_id_new, rule_id=rule_id_new,
321 **rule_update_data)
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300322
323 # Check that actual BW while downloading file
324 # is as expected (Update BW)
325 utils.wait_until_true(lambda: self._check_bw(
326 ssh_client,
327 self.fip['floating_ip_address'],
Maciej Józefczyk41b80192020-03-03 17:10:57 +0100328 port=self.NC_PORT,
329 expected_bw=QoSTest.LIMIT_BYTES_SEC * 3),
330 timeout=self.CHECK_TIMEOUT,
Arkady Shtemplerc2b80702020-10-10 15:15:43 +0300331 sleep=1,
332 exception=RuntimeError(
333 'Failed scenario: "Update QoS policy associated with'
334 ' the VM port" Actual BW is not as expected!'))
nfridman55f2ee62019-12-17 03:06:12 -0500335
336 @decorators.idempotent_id('66e5673e-0522-11ea-8d71-362b9e155667')
337 def test_attach_previously_used_port_to_new_instance(self):
338 """The test spawns new instance using port with QoS policy.
339
340        Ports with attached QoS policy could be used multiple times.
341        The policy rules have to be enforced on the new machines.
342 """
343 self.network = self.create_network()
344 self.subnet = self.create_subnet(self.network)
345 self.router = self.create_router_by_client()
346 self.create_router_interface(self.router['id'], self.subnet['id'])
347
348 vm, vm_port = self._create_server_by_port()
349
350 port_policy = self.os_admin.network_client.create_qos_policy(
351 name='port-policy',
352 description='policy for attach',
353 shared=False)['policy']
354
zahlabut7ebb66e2021-09-01 22:39:49 +0300355 rule_data = {
356 'max_kbps': constants.LIMIT_KILO_BITS_PER_SECOND,
357 'max_burst_kbps': constants.LIMIT_KILO_BITS_PER_SECOND}
358 rule = self._create_qos_bw_limit_rule(port_policy['id'], rule_data)
nfridman55f2ee62019-12-17 03:06:12 -0500359
360 self.os_admin.network_client.update_port(
361 vm_port['id'], qos_policy_id=port_policy['id'])
362
363 self.os_primary.servers_client.delete_server(vm['server']['id'])
364 waiters.wait_for_server_termination(
365 self.os_primary.servers_client,
366 vm['server']['id'])
367
368 # Launch a new server using the same port with attached policy
369 self._create_server_by_port(port=vm_port)
370
371 retrieved_port = self.os_admin.network_client.show_port(
372 vm_port['id'])
373 self.assertEqual(port_policy['id'],
374 retrieved_port['port']['qos_policy_id'],
375 """The expected policy ID is {0},
376 the actual value is {1}""".
377 format(port_policy['id'],
378 retrieved_port['port']['qos_policy_id']))
379
380 retrieved_policy = self.os_admin.network_client.show_qos_policy(
381 retrieved_port['port']['qos_policy_id'])
382
383 retrieved_rule_id = retrieved_policy['policy']['rules'][0]['id']
384 self.assertEqual(rule['id'],
385 retrieved_rule_id,
386 """The expected rule ID is {0},
387 the actual value is {1}""".
388 format(rule['id'], retrieved_rule_id))
nfridman5451fab2020-03-12 11:17:43 +0200389
390 @decorators.idempotent_id('4eee64da-5646-11ea-82b4-0242ac130003')
391 def test_create_instance_using_network_with_existing_policy(self):
392 network = self.create_network()
393
394 qos_policy = self.os_admin.network_client.create_qos_policy(
395 name='network-policy',
396 shared=False)['policy']
397
zahlabut7ebb66e2021-09-01 22:39:49 +0300398 rule_data = {
399 'max_kbps': constants.LIMIT_KILO_BITS_PER_SECOND,
400 'max_burst_kbps': constants.LIMIT_KILO_BITS_PER_SECOND}
401 rule = self._create_qos_bw_limit_rule(qos_policy['id'], rule_data)
nfridman5451fab2020-03-12 11:17:43 +0200402
403 network = self.os_admin.network_client.update_network(
404 network['id'],
405 qos_policy_id=qos_policy['id'])['network']
406 self.setup_network_and_server(network=network)
407 retrieved_net = self.client.show_network(network['id'])
408 self.assertEqual(qos_policy['id'],
409 retrieved_net['network']['qos_policy_id'],
410 """The expected policy ID is {0},
411 the actual value is {1}""".
412 format(qos_policy['id'],
413 retrieved_net['network']['qos_policy_id']))
414
415 retrieved_policy = self.os_admin.network_client.show_qos_policy(
416 retrieved_net['network']['qos_policy_id'])
417 retrieved_rule_id = retrieved_policy['policy']['rules'][0]['id']
418
zahlabut7ebb66e2021-09-01 22:39:49 +0300419 self.assertEqual(rule['id'],
nfridman5451fab2020-03-12 11:17:43 +0200420 retrieved_rule_id,
421 """The expected rule ID is {0},
422 the actual value is {1}""".
zahlabut7ebb66e2021-09-01 22:39:49 +0300423 format(rule['id'],
nfridman5451fab2020-03-12 11:17:43 +0200424 retrieved_rule_id))