blob: 9e1f4360804663c3ce733d531a48db971d967c8f [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
Sławek Kapłońskic0caa2e2017-02-25 10:11:32 +000023from tempest.lib import decorators
Itzik Brown1ef813a2016-06-06 12:56:21 +000024
Chandan Kumar667d3d32017-09-22 12:24:06 +053025from neutron_tempest_plugin.api import base as base_api
Chandan Kumar667d3d32017-09-22 12:24:06 +053026from neutron_tempest_plugin.common import ssh
27from neutron_tempest_plugin.common import utils
28from neutron_tempest_plugin import config
29from neutron_tempest_plugin.scenario import base
30from neutron_tempest_plugin.scenario import constants
31from neutron_tempest_plugin.scenario import exceptions as sc_exceptions
Itzik Brown1ef813a2016-06-06 12:56:21 +000032
33CONF = config.CONF
34LOG = logging.getLogger(__name__)
35
36
Rodolfo Alonso Hernandezfa5ebc42019-07-26 17:02:40 +000037def _try_connect(host_ip, port, socket_timeout):
Itzik Brown1ef813a2016-06-06 12:56:21 +000038 try:
39 client_socket = socket.socket(socket.AF_INET,
40 socket.SOCK_STREAM)
41 client_socket.connect((host_ip, port))
Rodolfo Alonso Hernandezfa5ebc42019-07-26 17:02:40 +000042 client_socket.settimeout(socket_timeout)
Itzik Brown1ef813a2016-06-06 12:56:21 +000043 return client_socket
44 except socket.error as serr:
45 if serr.errno == errno.ECONNREFUSED:
46 raise sc_exceptions.SocketConnectionRefused(host=host_ip,
47 port=port)
48 else:
49 raise
50
51
Rodolfo Alonso Hernandezfa5ebc42019-07-26 17:02:40 +000052def _connect_socket(host, port, socket_timeout):
Brian Haleyaee61ac2018-10-09 20:00:27 -040053 """Try to initiate a connection to a host using an ip address and a port.
Itzik Brown1ef813a2016-06-06 12:56:21 +000054
55 Trying couple of times until a timeout is reached in case the listening
56 host is not ready yet.
57 """
58
59 start = time.time()
60 while True:
61 try:
Rodolfo Alonso Hernandezfa5ebc42019-07-26 17:02:40 +000062 return _try_connect(host, port, socket_timeout)
Itzik Brown1ef813a2016-06-06 12:56:21 +000063 except sc_exceptions.SocketConnectionRefused:
64 if time.time() - start > constants.SOCKET_CONNECT_TIMEOUT:
65 raise sc_exceptions.ConnectionTimeoutException(host=host,
66 port=port)
67
68
YAMAMOTO Takashia2cc2e52018-07-31 18:54:02 +090069class QoSTestMixin(object):
Itzik Brown1ef813a2016-06-06 12:56:21 +000070 credentials = ['primary', 'admin']
71 force_tenant_isolation = False
72
Itzik Brown1ef813a2016-06-06 12:56:21 +000073 TOLERANCE_FACTOR = 1.5
Rodolfo Alonso Hernandezfa5ebc42019-07-26 17:02:40 +000074 BUFFER_SIZE = 512
Brian Haley33ef4602018-04-26 14:37:49 -040075 LIMIT_BYTES_SEC = (constants.LIMIT_KILO_BITS_PER_SECOND * 1024 *
76 TOLERANCE_FACTOR / 8.0)
LIU Yulong5ba88ef2017-12-22 10:50:15 +080077 NC_PORT = 1234
Maciej Józefczyk41b80192020-03-03 17:10:57 +010078 DOWNLOAD_DURATION = 5
79 # NOTE(mjozefcz): This makes around 10 retries.
80 CHECK_TIMEOUT = DOWNLOAD_DURATION * 10
Itzik Brown1ef813a2016-06-06 12:56:21 +000081
Rodolfo Alonso Hernandezfa5ebc42019-07-26 17:02:40 +000082 def _check_bw(self, ssh_client, host, port, expected_bw=LIMIT_BYTES_SEC):
Maciej Józefczyk328edc82019-09-16 14:05:48 +000083 utils.kill_nc_process(ssh_client)
Slawek Kaplonskifd4141f2020-03-14 14:34:00 +010084 self.ensure_nc_listen(ssh_client, port, "tcp")
Miguel Angel Ajod47e21a2017-02-07 16:21:16 +010085
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +030086 # Open TCP socket to remote VM and download big file
Miguel Angel Ajod47e21a2017-02-07 16:21:16 +010087 start_time = time.time()
Maciej Józefczyk41b80192020-03-03 17:10:57 +010088 client_socket = _connect_socket(
89 host, port, constants.SOCKET_CONNECT_TIMEOUT)
Miguel Angel Ajod47e21a2017-02-07 16:21:16 +010090 total_bytes_read = 0
Rodolfo Alonso Hernandezfa5ebc42019-07-26 17:02:40 +000091 try:
Maciej Józefczyk41b80192020-03-03 17:10:57 +010092 while time.time() - start_time < self.DOWNLOAD_DURATION:
Rodolfo Alonso Hernandezfa5ebc42019-07-26 17:02:40 +000093 data = client_socket.recv(self.BUFFER_SIZE)
94 total_bytes_read += len(data)
Itzik Brown1ef813a2016-06-06 12:56:21 +000095
Rodolfo Alonso Hernandezfa5ebc42019-07-26 17:02:40 +000096 # Calculate and return actual BW + logging result
97 time_elapsed = time.time() - start_time
98 bytes_per_second = total_bytes_read / time_elapsed
Miguel Angel Ajod47e21a2017-02-07 16:21:16 +010099
Rodolfo Alonso Hernandezfa5ebc42019-07-26 17:02:40 +0000100 LOG.debug("time_elapsed = %(time_elapsed).16f, "
101 "total_bytes_read = %(total_bytes_read)d, "
Maciej Józefczyk41b80192020-03-03 17:10:57 +0100102 "bytes_per_second = %(bytes_per_second)d, "
103 "expected_bw = %(expected_bw)d.",
Rodolfo Alonso Hernandezfa5ebc42019-07-26 17:02:40 +0000104 {'time_elapsed': time_elapsed,
105 'total_bytes_read': total_bytes_read,
Maciej Józefczyk41b80192020-03-03 17:10:57 +0100106 'bytes_per_second': bytes_per_second,
107 'expected_bw': expected_bw})
Rodolfo Alonso Hernandezfa5ebc42019-07-26 17:02:40 +0000108 return bytes_per_second <= expected_bw
109 except socket.timeout:
110 LOG.warning('Socket timeout while reading the remote file, bytes '
111 'read: %s', total_bytes_read)
Maciej Józefczyk328edc82019-09-16 14:05:48 +0000112 utils.kill_nc_process(ssh_client)
Rodolfo Alonso Hernandezfa5ebc42019-07-26 17:02:40 +0000113 return False
114 finally:
115 client_socket.close()
Itzik Brown1ef813a2016-06-06 12:56:21 +0000116
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800117 def _create_ssh_client(self):
118 return ssh.Client(self.fip['floating_ip_address'],
119 CONF.validation.image_ssh_user,
120 pkey=self.keypair['private_key'])
121
122 def _test_basic_resources(self):
123 self.setup_network_and_server()
124 self.check_connectivity(self.fip['floating_ip_address'],
125 CONF.validation.image_ssh_user,
126 self.keypair['private_key'])
127 rulesets = [{'protocol': 'tcp',
128 'direction': 'ingress',
129 'port_range_min': self.NC_PORT,
130 'port_range_max': self.NC_PORT,
131 'remote_ip_prefix': '0.0.0.0/0'}]
132 self.create_secgroup_rules(rulesets,
133 self.security_groups[-1]['id'])
134
135 def _create_qos_policy(self):
136 policy = self.os_admin.network_client.create_qos_policy(
137 name='test-policy',
138 description='test-qos-policy',
139 shared=True)
140 return policy['policy']['id']
141
nfridman55f2ee62019-12-17 03:06:12 -0500142 def _create_server_by_port(self, port=None):
143 """Launch an instance using a port interface;
144
145 In case that the given port is None, a new port is created,
146 activated and configured with inbound SSH and TCP connection.
147 """
148 # Create and activate the port that will be assign to the instance.
149 if port is None:
150 secgroup = self.create_security_group()
151 self.create_loginable_secgroup_rule(
152 secgroup_id=secgroup['id'])
153
154 secgroup_rules = [{'protocol': 'tcp',
155 'direction': 'ingress',
156 'port_range_min': self.NC_PORT,
157 'port_range_max': self.NC_PORT,
158 'remote_ip_prefix': '0.0.0.0/0'}]
159
160 self.create_secgroup_rules(secgroup_rules,
161 secgroup['id'])
162
163 port = self.create_port(self.network,
164 security_groups=[secgroup['id']])
165 self.fip = self.create_floatingip(port=port)
166
167 keypair = self.create_keypair()
168
169 server_kwargs = {
170 'flavor_ref': CONF.compute.flavor_ref,
171 'image_ref': CONF.compute.image_ref,
172 'key_name': keypair['name'],
173 'networks': [{'port': port['id']}],
174 }
175
176 server = self.create_server(**server_kwargs)
177 self.wait_for_server_active(server['server'])
178 self.check_connectivity(self.fip['floating_ip_address'],
179 CONF.validation.image_ssh_user,
180 keypair['private_key'])
181 return server, port
182
YAMAMOTO Takashia2cc2e52018-07-31 18:54:02 +0900183
184class QoSTest(QoSTestMixin, base.BaseTempestTestCase):
185 @classmethod
186 @tutils.requires_ext(extension="qos", service="network")
187 @base_api.require_qos_rule_type(qos_consts.RULE_TYPE_BANDWIDTH_LIMIT)
188 def resource_setup(cls):
189 super(QoSTest, cls).resource_setup()
190
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300191 @decorators.idempotent_id('00682a0c-b72e-11e8-b81e-8c16450ea513')
192 def test_qos_basic_and_update(self):
Arkady Shtemplerc2b80702020-10-10 15:15:43 +0300193 """This test covers following scenarios:
Itzik Brown1ef813a2016-06-06 12:56:21 +0000194
Arkady Shtemplerc2b80702020-10-10 15:15:43 +0300195 1) Create a QoS policy associated with the network.
196 Expected result: BW is limited according the values set in
197 QoS policy rule.
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300198
Arkady Shtemplerc2b80702020-10-10 15:15:43 +0300199 2) Update QoS policy associated with the network.
200 Expected result: BW is limited according the new values
201 set in QoS policy rule.
202
203 3) Create a new QoS policy associated with the VM port.
204 Expected result: BW is limited according the values set in
205 new QoS policy rule.
206 Note: Neutron port is prioritized higher than Network, means
207 that: "Neutron Port Priority" is also covered.
208
209 4) Update QoS policy associated with the VM port.
210 Expected result: BW is limited according the new values set
211 in QoS policy rule.
212
Itzik Brown1ef813a2016-06-06 12:56:21 +0000213 """
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300214
215 # Setup resources
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800216 self._test_basic_resources()
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800217 ssh_client = self._create_ssh_client()
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300218
219 # Create QoS policy
220 bw_limit_policy_id = self._create_qos_policy()
221
222 # As admin user create QoS rule
223 rule_id = self.os_admin.network_client.create_bandwidth_limit_rule(
224 policy_id=bw_limit_policy_id,
225 max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND,
226 max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND)[
227 'bandwidth_limit_rule']['id']
228
229 # Associate QoS to the network
230 self.os_admin.network_client.update_network(
231 self.network['id'], qos_policy_id=bw_limit_policy_id)
232
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300233 # Basic test, Check that actual BW while downloading file
234 # is as expected (Original BW)
Itzik Brown1ef813a2016-06-06 12:56:21 +0000235 utils.wait_until_true(lambda: self._check_bw(
236 ssh_client,
237 self.fip['floating_ip_address'],
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800238 port=self.NC_PORT),
Maciej Józefczyk41b80192020-03-03 17:10:57 +0100239 timeout=self.CHECK_TIMEOUT,
Arkady Shtemplerc2b80702020-10-10 15:15:43 +0300240 sleep=1,
241 exception=RuntimeError(
242 'Failed scenario: "Create a QoS policy associated with'
243 ' the network" Actual BW is not as expected!'))
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300244
245 # As admin user update QoS rule
246 self.os_admin.network_client.update_bandwidth_limit_rule(
247 bw_limit_policy_id,
248 rule_id,
249 max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND * 2,
250 max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND * 2)
251
252 # Check that actual BW while downloading file
253 # is as expected (Update BW)
254 utils.wait_until_true(lambda: self._check_bw(
255 ssh_client,
256 self.fip['floating_ip_address'],
257 port=self.NC_PORT,
258 expected_bw=QoSTest.LIMIT_BYTES_SEC * 2),
Maciej Józefczyk41b80192020-03-03 17:10:57 +0100259 timeout=self.CHECK_TIMEOUT,
Arkady Shtemplerc2b80702020-10-10 15:15:43 +0300260 sleep=1,
261 exception=RuntimeError(
262 'Failed scenario: "Update QoS policy associated with'
263 ' the network" Actual BW is not as expected!'))
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300264
265 # Create a new QoS policy
266 bw_limit_policy_id_new = self._create_qos_policy()
267
268 # As admin user create a new QoS rule
269 rule_id_new = self.os_admin.network_client.create_bandwidth_limit_rule(
270 policy_id=bw_limit_policy_id_new,
271 max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND,
272 max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND)[
273 'bandwidth_limit_rule']['id']
274
275 # Associate a new QoS policy to Neutron port
276 self.os_admin.network_client.update_port(
277 self.port['id'], qos_policy_id=bw_limit_policy_id_new)
278
279 # Check that actual BW while downloading file
280 # is as expected (Original BW)
281 utils.wait_until_true(lambda: self._check_bw(
282 ssh_client,
283 self.fip['floating_ip_address'],
284 port=self.NC_PORT),
Maciej Józefczyk41b80192020-03-03 17:10:57 +0100285 timeout=self.CHECK_TIMEOUT,
Arkady Shtemplerc2b80702020-10-10 15:15:43 +0300286 sleep=1,
287 exception=RuntimeError(
288 'Failed scenario: "Create a new QoS policy associated with'
289 ' the VM port" Actual BW is not as expected!'))
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300290
291 # As admin user update QoS rule
292 self.os_admin.network_client.update_bandwidth_limit_rule(
293 bw_limit_policy_id_new,
294 rule_id_new,
295 max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND * 3,
296 max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND * 3)
297
298 # Check that actual BW while downloading file
299 # is as expected (Update BW)
300 utils.wait_until_true(lambda: self._check_bw(
301 ssh_client,
302 self.fip['floating_ip_address'],
Maciej Józefczyk41b80192020-03-03 17:10:57 +0100303 port=self.NC_PORT,
304 expected_bw=QoSTest.LIMIT_BYTES_SEC * 3),
305 timeout=self.CHECK_TIMEOUT,
Arkady Shtemplerc2b80702020-10-10 15:15:43 +0300306 sleep=1,
307 exception=RuntimeError(
308 'Failed scenario: "Update QoS policy associated with'
309 ' the VM port" Actual BW is not as expected!'))
nfridman55f2ee62019-12-17 03:06:12 -0500310
311 @decorators.idempotent_id('66e5673e-0522-11ea-8d71-362b9e155667')
312 def test_attach_previously_used_port_to_new_instance(self):
313 """The test spawns new instance using port with QoS policy.
314
315        Ports with attached QoS policy could be used multiple times.
316        The policy rules have to be enforced on the new machines.
317 """
318 self.network = self.create_network()
319 self.subnet = self.create_subnet(self.network)
320 self.router = self.create_router_by_client()
321 self.create_router_interface(self.router['id'], self.subnet['id'])
322
323 vm, vm_port = self._create_server_by_port()
324
325 port_policy = self.os_admin.network_client.create_qos_policy(
326 name='port-policy',
327 description='policy for attach',
328 shared=False)['policy']
329
330 rule = self.os_admin.network_client.create_bandwidth_limit_rule(
331 policy_id=port_policy['id'],
332 max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND,
333 max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND)[
334 'bandwidth_limit_rule']
335
336 self.os_admin.network_client.update_port(
337 vm_port['id'], qos_policy_id=port_policy['id'])
338
339 self.os_primary.servers_client.delete_server(vm['server']['id'])
340 waiters.wait_for_server_termination(
341 self.os_primary.servers_client,
342 vm['server']['id'])
343
344 # Launch a new server using the same port with attached policy
345 self._create_server_by_port(port=vm_port)
346
347 retrieved_port = self.os_admin.network_client.show_port(
348 vm_port['id'])
349 self.assertEqual(port_policy['id'],
350 retrieved_port['port']['qos_policy_id'],
351 """The expected policy ID is {0},
352 the actual value is {1}""".
353 format(port_policy['id'],
354 retrieved_port['port']['qos_policy_id']))
355
356 retrieved_policy = self.os_admin.network_client.show_qos_policy(
357 retrieved_port['port']['qos_policy_id'])
358
359 retrieved_rule_id = retrieved_policy['policy']['rules'][0]['id']
360 self.assertEqual(rule['id'],
361 retrieved_rule_id,
362 """The expected rule ID is {0},
363 the actual value is {1}""".
364 format(rule['id'], retrieved_rule_id))
nfridman5451fab2020-03-12 11:17:43 +0200365
366 @decorators.idempotent_id('4eee64da-5646-11ea-82b4-0242ac130003')
367 def test_create_instance_using_network_with_existing_policy(self):
368 network = self.create_network()
369
370 qos_policy = self.os_admin.network_client.create_qos_policy(
371 name='network-policy',
372 shared=False)['policy']
373
374 rule = self.os_admin.network_client.create_bandwidth_limit_rule(
375 policy_id=qos_policy['id'],
376 max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND,
377 max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND)
378
379 network = self.os_admin.network_client.update_network(
380 network['id'],
381 qos_policy_id=qos_policy['id'])['network']
382 self.setup_network_and_server(network=network)
383 retrieved_net = self.client.show_network(network['id'])
384 self.assertEqual(qos_policy['id'],
385 retrieved_net['network']['qos_policy_id'],
386 """The expected policy ID is {0},
387 the actual value is {1}""".
388 format(qos_policy['id'],
389 retrieved_net['network']['qos_policy_id']))
390
391 retrieved_policy = self.os_admin.network_client.show_qos_policy(
392 retrieved_net['network']['qos_policy_id'])
393 retrieved_rule_id = retrieved_policy['policy']['rules'][0]['id']
394
395 self.assertEqual(rule['bandwidth_limit_rule']['id'],
396 retrieved_rule_id,
397 """The expected rule ID is {0},
398 the actual value is {1}""".
399 format(rule['bandwidth_limit_rule']['id'],
400 retrieved_rule_id))