blob: 938d2b044c159780bf62120912fca42adadd6f67 [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):
193 """This test covers both:
Itzik Brown1ef813a2016-06-06 12:56:21 +0000194
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300195 1) Basic QoS functionality
196 This is a basic test that check that a QoS policy with
197 a bandwidth limit rule is applied correctly by sending
198 a file from the instance to the test node.
199 Then calculating the bandwidth every ~1 sec by the number of bits
200 received / elapsed time.
201
202 2) Update QoS policy
203 Administrator has the ability to update existing QoS policy,
204 this test is planned to verify that:
205 - actual BW is affected as expected after updating QoS policy.
206 Test scenario:
207 1) Associating QoS Policy with "Original_bandwidth"
208 to the test node
209 2) BW validation - by downloading file on test node.
210 ("Original_bandwidth" is expected)
211 3) Updating existing QoS Policy to a new BW value
212 "Updated_bandwidth"
213 4) BW validation - by downloading file on test node.
214 ("Updated_bandwidth" is expected)
215 Note:
216 There are two options to associate QoS policy to VM:
217 "Neutron Port" or "Network", in this test
218 both options are covered.
Itzik Brown1ef813a2016-06-06 12:56:21 +0000219 """
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300220
221 # Setup resources
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800222 self._test_basic_resources()
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800223 ssh_client = self._create_ssh_client()
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300224
225 # Create QoS policy
226 bw_limit_policy_id = self._create_qos_policy()
227
228 # As admin user create QoS rule
229 rule_id = self.os_admin.network_client.create_bandwidth_limit_rule(
230 policy_id=bw_limit_policy_id,
231 max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND,
232 max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND)[
233 'bandwidth_limit_rule']['id']
234
235 # Associate QoS to the network
236 self.os_admin.network_client.update_network(
237 self.network['id'], qos_policy_id=bw_limit_policy_id)
238
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300239 # Basic test, Check that actual BW while downloading file
240 # is as expected (Original BW)
Itzik Brown1ef813a2016-06-06 12:56:21 +0000241 utils.wait_until_true(lambda: self._check_bw(
242 ssh_client,
243 self.fip['floating_ip_address'],
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800244 port=self.NC_PORT),
Maciej Józefczyk41b80192020-03-03 17:10:57 +0100245 timeout=self.CHECK_TIMEOUT,
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300246 sleep=1)
247
248 # As admin user update QoS rule
249 self.os_admin.network_client.update_bandwidth_limit_rule(
250 bw_limit_policy_id,
251 rule_id,
252 max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND * 2,
253 max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND * 2)
254
255 # Check that actual BW while downloading file
256 # is as expected (Update BW)
257 utils.wait_until_true(lambda: self._check_bw(
258 ssh_client,
259 self.fip['floating_ip_address'],
260 port=self.NC_PORT,
261 expected_bw=QoSTest.LIMIT_BYTES_SEC * 2),
Maciej Józefczyk41b80192020-03-03 17:10:57 +0100262 timeout=self.CHECK_TIMEOUT,
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300263 sleep=1)
264
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 Shtempler3e1d8f12018-08-19 10:36:24 +0300286 sleep=1)
287
288 # As admin user update QoS rule
289 self.os_admin.network_client.update_bandwidth_limit_rule(
290 bw_limit_policy_id_new,
291 rule_id_new,
292 max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND * 3,
293 max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND * 3)
294
295 # Check that actual BW while downloading file
296 # is as expected (Update BW)
297 utils.wait_until_true(lambda: self._check_bw(
298 ssh_client,
299 self.fip['floating_ip_address'],
Maciej Józefczyk41b80192020-03-03 17:10:57 +0100300 port=self.NC_PORT,
301 expected_bw=QoSTest.LIMIT_BYTES_SEC * 3),
302 timeout=self.CHECK_TIMEOUT,
Itzik Brown1ef813a2016-06-06 12:56:21 +0000303 sleep=1)
nfridman55f2ee62019-12-17 03:06:12 -0500304
305 @decorators.idempotent_id('66e5673e-0522-11ea-8d71-362b9e155667')
306 def test_attach_previously_used_port_to_new_instance(self):
307 """The test spawns new instance using port with QoS policy.
308
309        Ports with attached QoS policy could be used multiple times.
310        The policy rules have to be enforced on the new machines.
311 """
312 self.network = self.create_network()
313 self.subnet = self.create_subnet(self.network)
314 self.router = self.create_router_by_client()
315 self.create_router_interface(self.router['id'], self.subnet['id'])
316
317 vm, vm_port = self._create_server_by_port()
318
319 port_policy = self.os_admin.network_client.create_qos_policy(
320 name='port-policy',
321 description='policy for attach',
322 shared=False)['policy']
323
324 rule = self.os_admin.network_client.create_bandwidth_limit_rule(
325 policy_id=port_policy['id'],
326 max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND,
327 max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND)[
328 'bandwidth_limit_rule']
329
330 self.os_admin.network_client.update_port(
331 vm_port['id'], qos_policy_id=port_policy['id'])
332
333 self.os_primary.servers_client.delete_server(vm['server']['id'])
334 waiters.wait_for_server_termination(
335 self.os_primary.servers_client,
336 vm['server']['id'])
337
338 # Launch a new server using the same port with attached policy
339 self._create_server_by_port(port=vm_port)
340
341 retrieved_port = self.os_admin.network_client.show_port(
342 vm_port['id'])
343 self.assertEqual(port_policy['id'],
344 retrieved_port['port']['qos_policy_id'],
345 """The expected policy ID is {0},
346 the actual value is {1}""".
347 format(port_policy['id'],
348 retrieved_port['port']['qos_policy_id']))
349
350 retrieved_policy = self.os_admin.network_client.show_qos_policy(
351 retrieved_port['port']['qos_policy_id'])
352
353 retrieved_rule_id = retrieved_policy['policy']['rules'][0]['id']
354 self.assertEqual(rule['id'],
355 retrieved_rule_id,
356 """The expected rule ID is {0},
357 the actual value is {1}""".
358 format(rule['id'], retrieved_rule_id))
nfridman5451fab2020-03-12 11:17:43 +0200359
360 @decorators.idempotent_id('4eee64da-5646-11ea-82b4-0242ac130003')
361 def test_create_instance_using_network_with_existing_policy(self):
362 network = self.create_network()
363
364 qos_policy = self.os_admin.network_client.create_qos_policy(
365 name='network-policy',
366 shared=False)['policy']
367
368 rule = self.os_admin.network_client.create_bandwidth_limit_rule(
369 policy_id=qos_policy['id'],
370 max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND,
371 max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND)
372
373 network = self.os_admin.network_client.update_network(
374 network['id'],
375 qos_policy_id=qos_policy['id'])['network']
376 self.setup_network_and_server(network=network)
377 retrieved_net = self.client.show_network(network['id'])
378 self.assertEqual(qos_policy['id'],
379 retrieved_net['network']['qos_policy_id'],
380 """The expected policy ID is {0},
381 the actual value is {1}""".
382 format(qos_policy['id'],
383 retrieved_net['network']['qos_policy_id']))
384
385 retrieved_policy = self.os_admin.network_client.show_qos_policy(
386 retrieved_net['network']['qos_policy_id'])
387 retrieved_rule_id = retrieved_policy['policy']['rules'][0]['id']
388
389 self.assertEqual(rule['bandwidth_limit_rule']['id'],
390 retrieved_rule_id,
391 """The expected rule ID is {0},
392 the actual value is {1}""".
393 format(rule['bandwidth_limit_rule']['id'],
394 retrieved_rule_id))