blob: 77520a7a461887ece7e7d87b85dd11b4c9c2c520 [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'])
Slawek Kaplonski2211eab2020-10-20 16:43:53 +0200178 self.wait_for_guest_os_ready(server['server'])
nfridman55f2ee62019-12-17 03:06:12 -0500179 self.check_connectivity(self.fip['floating_ip_address'],
180 CONF.validation.image_ssh_user,
181 keypair['private_key'])
182 return server, port
183
YAMAMOTO Takashia2cc2e52018-07-31 18:54:02 +0900184
185class QoSTest(QoSTestMixin, base.BaseTempestTestCase):
186 @classmethod
187 @tutils.requires_ext(extension="qos", service="network")
188 @base_api.require_qos_rule_type(qos_consts.RULE_TYPE_BANDWIDTH_LIMIT)
189 def resource_setup(cls):
190 super(QoSTest, cls).resource_setup()
191
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300192 @decorators.idempotent_id('00682a0c-b72e-11e8-b81e-8c16450ea513')
193 def test_qos_basic_and_update(self):
Arkady Shtemplerc2b80702020-10-10 15:15:43 +0300194 """This test covers following scenarios:
Itzik Brown1ef813a2016-06-06 12:56:21 +0000195
Arkady Shtemplerc2b80702020-10-10 15:15:43 +0300196 1) Create a QoS policy associated with the network.
197 Expected result: BW is limited according the values set in
198 QoS policy rule.
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300199
Arkady Shtemplerc2b80702020-10-10 15:15:43 +0300200 2) Update QoS policy associated with the network.
201 Expected result: BW is limited according the new values
202 set in QoS policy rule.
203
204 3) Create a new QoS policy associated with the VM port.
205 Expected result: BW is limited according the values set in
206 new QoS policy rule.
207 Note: Neutron port is prioritized higher than Network, means
208 that: "Neutron Port Priority" is also covered.
209
210 4) Update QoS policy associated with the VM port.
211 Expected result: BW is limited according the new values set
212 in QoS policy rule.
213
Itzik Brown1ef813a2016-06-06 12:56:21 +0000214 """
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300215
216 # Setup resources
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800217 self._test_basic_resources()
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800218 ssh_client = self._create_ssh_client()
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300219
220 # Create QoS policy
221 bw_limit_policy_id = self._create_qos_policy()
222
223 # As admin user create QoS rule
224 rule_id = self.os_admin.network_client.create_bandwidth_limit_rule(
225 policy_id=bw_limit_policy_id,
226 max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND,
227 max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND)[
228 'bandwidth_limit_rule']['id']
229
230 # Associate QoS to the network
231 self.os_admin.network_client.update_network(
232 self.network['id'], qos_policy_id=bw_limit_policy_id)
233
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300234 # Basic test, Check that actual BW while downloading file
235 # is as expected (Original BW)
Itzik Brown1ef813a2016-06-06 12:56:21 +0000236 utils.wait_until_true(lambda: self._check_bw(
237 ssh_client,
238 self.fip['floating_ip_address'],
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800239 port=self.NC_PORT),
Maciej Józefczyk41b80192020-03-03 17:10:57 +0100240 timeout=self.CHECK_TIMEOUT,
Arkady Shtemplerc2b80702020-10-10 15:15:43 +0300241 sleep=1,
242 exception=RuntimeError(
243 'Failed scenario: "Create a QoS policy associated with'
244 ' the network" Actual BW is not as expected!'))
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300245
246 # As admin user update QoS rule
247 self.os_admin.network_client.update_bandwidth_limit_rule(
248 bw_limit_policy_id,
249 rule_id,
250 max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND * 2,
251 max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND * 2)
252
253 # Check that actual BW while downloading file
254 # is as expected (Update BW)
255 utils.wait_until_true(lambda: self._check_bw(
256 ssh_client,
257 self.fip['floating_ip_address'],
258 port=self.NC_PORT,
259 expected_bw=QoSTest.LIMIT_BYTES_SEC * 2),
Maciej Józefczyk41b80192020-03-03 17:10:57 +0100260 timeout=self.CHECK_TIMEOUT,
Arkady Shtemplerc2b80702020-10-10 15:15:43 +0300261 sleep=1,
262 exception=RuntimeError(
263 'Failed scenario: "Update QoS policy associated with'
264 ' the network" Actual BW is not as expected!'))
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300265
266 # Create a new QoS policy
267 bw_limit_policy_id_new = self._create_qos_policy()
268
269 # As admin user create a new QoS rule
270 rule_id_new = self.os_admin.network_client.create_bandwidth_limit_rule(
271 policy_id=bw_limit_policy_id_new,
272 max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND,
273 max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND)[
274 'bandwidth_limit_rule']['id']
275
276 # Associate a new QoS policy to Neutron port
277 self.os_admin.network_client.update_port(
278 self.port['id'], qos_policy_id=bw_limit_policy_id_new)
279
280 # Check that actual BW while downloading file
281 # is as expected (Original BW)
282 utils.wait_until_true(lambda: self._check_bw(
283 ssh_client,
284 self.fip['floating_ip_address'],
285 port=self.NC_PORT),
Maciej Józefczyk41b80192020-03-03 17:10:57 +0100286 timeout=self.CHECK_TIMEOUT,
Arkady Shtemplerc2b80702020-10-10 15:15:43 +0300287 sleep=1,
288 exception=RuntimeError(
289 'Failed scenario: "Create a new QoS policy associated with'
290 ' the VM port" Actual BW is not as expected!'))
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300291
292 # As admin user update QoS rule
293 self.os_admin.network_client.update_bandwidth_limit_rule(
294 bw_limit_policy_id_new,
295 rule_id_new,
296 max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND * 3,
297 max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND * 3)
298
299 # Check that actual BW while downloading file
300 # is as expected (Update BW)
301 utils.wait_until_true(lambda: self._check_bw(
302 ssh_client,
303 self.fip['floating_ip_address'],
Maciej Józefczyk41b80192020-03-03 17:10:57 +0100304 port=self.NC_PORT,
305 expected_bw=QoSTest.LIMIT_BYTES_SEC * 3),
306 timeout=self.CHECK_TIMEOUT,
Arkady Shtemplerc2b80702020-10-10 15:15:43 +0300307 sleep=1,
308 exception=RuntimeError(
309 'Failed scenario: "Update QoS policy associated with'
310 ' the VM port" Actual BW is not as expected!'))
nfridman55f2ee62019-12-17 03:06:12 -0500311
312 @decorators.idempotent_id('66e5673e-0522-11ea-8d71-362b9e155667')
313 def test_attach_previously_used_port_to_new_instance(self):
314 """The test spawns new instance using port with QoS policy.
315
316        Ports with attached QoS policy could be used multiple times.
317        The policy rules have to be enforced on the new machines.
318 """
319 self.network = self.create_network()
320 self.subnet = self.create_subnet(self.network)
321 self.router = self.create_router_by_client()
322 self.create_router_interface(self.router['id'], self.subnet['id'])
323
324 vm, vm_port = self._create_server_by_port()
325
326 port_policy = self.os_admin.network_client.create_qos_policy(
327 name='port-policy',
328 description='policy for attach',
329 shared=False)['policy']
330
331 rule = self.os_admin.network_client.create_bandwidth_limit_rule(
332 policy_id=port_policy['id'],
333 max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND,
334 max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND)[
335 'bandwidth_limit_rule']
336
337 self.os_admin.network_client.update_port(
338 vm_port['id'], qos_policy_id=port_policy['id'])
339
340 self.os_primary.servers_client.delete_server(vm['server']['id'])
341 waiters.wait_for_server_termination(
342 self.os_primary.servers_client,
343 vm['server']['id'])
344
345 # Launch a new server using the same port with attached policy
346 self._create_server_by_port(port=vm_port)
347
348 retrieved_port = self.os_admin.network_client.show_port(
349 vm_port['id'])
350 self.assertEqual(port_policy['id'],
351 retrieved_port['port']['qos_policy_id'],
352 """The expected policy ID is {0},
353 the actual value is {1}""".
354 format(port_policy['id'],
355 retrieved_port['port']['qos_policy_id']))
356
357 retrieved_policy = self.os_admin.network_client.show_qos_policy(
358 retrieved_port['port']['qos_policy_id'])
359
360 retrieved_rule_id = retrieved_policy['policy']['rules'][0]['id']
361 self.assertEqual(rule['id'],
362 retrieved_rule_id,
363 """The expected rule ID is {0},
364 the actual value is {1}""".
365 format(rule['id'], retrieved_rule_id))
nfridman5451fab2020-03-12 11:17:43 +0200366
367 @decorators.idempotent_id('4eee64da-5646-11ea-82b4-0242ac130003')
368 def test_create_instance_using_network_with_existing_policy(self):
369 network = self.create_network()
370
371 qos_policy = self.os_admin.network_client.create_qos_policy(
372 name='network-policy',
373 shared=False)['policy']
374
375 rule = self.os_admin.network_client.create_bandwidth_limit_rule(
376 policy_id=qos_policy['id'],
377 max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND,
378 max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND)
379
380 network = self.os_admin.network_client.update_network(
381 network['id'],
382 qos_policy_id=qos_policy['id'])['network']
383 self.setup_network_and_server(network=network)
384 retrieved_net = self.client.show_network(network['id'])
385 self.assertEqual(qos_policy['id'],
386 retrieved_net['network']['qos_policy_id'],
387 """The expected policy ID is {0},
388 the actual value is {1}""".
389 format(qos_policy['id'],
390 retrieved_net['network']['qos_policy_id']))
391
392 retrieved_policy = self.os_admin.network_client.show_qos_policy(
393 retrieved_net['network']['qos_policy_id'])
394 retrieved_rule_id = retrieved_policy['policy']['rules'][0]['id']
395
396 self.assertEqual(rule['bandwidth_limit_rule']['id'],
397 retrieved_rule_id,
398 """The expected rule ID is {0},
399 the actual value is {1}""".
400 format(rule['bandwidth_limit_rule']['id'],
401 retrieved_rule_id))