blob: e84fb3c1470b34195e49623f33091346826c86fa [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
Rodolfo Alonso Hernandezfa5ebc42019-07-26 17:02:40 +000073 FILE_SIZE = 1024 * 1024
Itzik Brown1ef813a2016-06-06 12:56:21 +000074 TOLERANCE_FACTOR = 1.5
Rodolfo Alonso Hernandezfa5ebc42019-07-26 17:02:40 +000075 BUFFER_SIZE = 512
76 COUNT = FILE_SIZE / BUFFER_SIZE
Brian Haley33ef4602018-04-26 14:37:49 -040077 LIMIT_BYTES_SEC = (constants.LIMIT_KILO_BITS_PER_SECOND * 1024 *
78 TOLERANCE_FACTOR / 8.0)
Itzik Brown1ef813a2016-06-06 12:56:21 +000079 FILE_PATH = "/tmp/img"
80
LIU Yulong5ba88ef2017-12-22 10:50:15 +080081 NC_PORT = 1234
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +030082 FILE_DOWNLOAD_TIMEOUT = 120
LIU Yulong5ba88ef2017-12-22 10:50:15 +080083
Itzik Brown1ef813a2016-06-06 12:56:21 +000084 def _create_file_for_bw_tests(self, ssh_client):
85 cmd = ("(dd if=/dev/zero bs=%(bs)d count=%(count)d of=%(file_path)s) "
Rodolfo Alonso Hernandezfa5ebc42019-07-26 17:02:40 +000086 % {'bs': self.BUFFER_SIZE, 'count': self.COUNT,
87 'file_path': self.FILE_PATH})
Rodolfo Alonso Hernandezaa65dfb2019-09-18 11:30:04 +000088 ssh_client.exec_command(cmd, timeout=5)
Rodolfo Alonso Hernandezfa5ebc42019-07-26 17:02:40 +000089 cmd = "stat -c %%s %s" % self.FILE_PATH
Rodolfo Alonso Hernandezaa65dfb2019-09-18 11:30:04 +000090 filesize = ssh_client.exec_command(cmd, timeout=5)
Rodolfo Alonso Hernandezfa5ebc42019-07-26 17:02:40 +000091 if int(filesize.strip()) != self.FILE_SIZE:
Itzik Brown1ef813a2016-06-06 12:56:21 +000092 raise sc_exceptions.FileCreationFailedException(
Rodolfo Alonso Hernandezfa5ebc42019-07-26 17:02:40 +000093 file=self.FILE_PATH)
Itzik Brown1ef813a2016-06-06 12:56:21 +000094
Rodolfo Alonso Hernandezfa5ebc42019-07-26 17:02:40 +000095 def _check_bw(self, ssh_client, host, port, expected_bw=LIMIT_BYTES_SEC):
Maciej Józefczyk328edc82019-09-16 14:05:48 +000096 utils.kill_nc_process(ssh_client)
Itzik Brown1ef813a2016-06-06 12:56:21 +000097 cmd = ("(nc -ll -p %(port)d < %(file_path)s > /dev/null &)" % {
Rodolfo Alonso Hernandezfa5ebc42019-07-26 17:02:40 +000098 'port': port, 'file_path': self.FILE_PATH})
Rodolfo Alonso Hernandezaa65dfb2019-09-18 11:30:04 +000099 ssh_client.exec_command(cmd, timeout=5)
Miguel Angel Ajod47e21a2017-02-07 16:21:16 +0100100
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300101 # Open TCP socket to remote VM and download big file
Miguel Angel Ajod47e21a2017-02-07 16:21:16 +0100102 start_time = time.time()
Rodolfo Alonso Hernandezfa5ebc42019-07-26 17:02:40 +0000103 socket_timeout = self.FILE_SIZE * self.TOLERANCE_FACTOR / expected_bw
104 client_socket = _connect_socket(host, port, socket_timeout)
Miguel Angel Ajod47e21a2017-02-07 16:21:16 +0100105 total_bytes_read = 0
Rodolfo Alonso Hernandezfa5ebc42019-07-26 17:02:40 +0000106 try:
107 while total_bytes_read < self.FILE_SIZE:
108 data = client_socket.recv(self.BUFFER_SIZE)
109 total_bytes_read += len(data)
Itzik Brown1ef813a2016-06-06 12:56:21 +0000110
Rodolfo Alonso Hernandezfa5ebc42019-07-26 17:02:40 +0000111 # Calculate and return actual BW + logging result
112 time_elapsed = time.time() - start_time
113 bytes_per_second = total_bytes_read / time_elapsed
Miguel Angel Ajod47e21a2017-02-07 16:21:16 +0100114
Rodolfo Alonso Hernandezfa5ebc42019-07-26 17:02:40 +0000115 LOG.debug("time_elapsed = %(time_elapsed).16f, "
116 "total_bytes_read = %(total_bytes_read)d, "
117 "bytes_per_second = %(bytes_per_second)d",
118 {'time_elapsed': time_elapsed,
119 'total_bytes_read': total_bytes_read,
120 'bytes_per_second': bytes_per_second})
121 return bytes_per_second <= expected_bw
122 except socket.timeout:
123 LOG.warning('Socket timeout while reading the remote file, bytes '
124 'read: %s', total_bytes_read)
Maciej Józefczyk328edc82019-09-16 14:05:48 +0000125 utils.kill_nc_process(ssh_client)
Rodolfo Alonso Hernandezfa5ebc42019-07-26 17:02:40 +0000126 return False
127 finally:
128 client_socket.close()
Itzik Brown1ef813a2016-06-06 12:56:21 +0000129
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800130 def _create_ssh_client(self):
131 return ssh.Client(self.fip['floating_ip_address'],
132 CONF.validation.image_ssh_user,
133 pkey=self.keypair['private_key'])
134
135 def _test_basic_resources(self):
136 self.setup_network_and_server()
137 self.check_connectivity(self.fip['floating_ip_address'],
138 CONF.validation.image_ssh_user,
139 self.keypair['private_key'])
140 rulesets = [{'protocol': 'tcp',
141 'direction': 'ingress',
142 'port_range_min': self.NC_PORT,
143 'port_range_max': self.NC_PORT,
144 'remote_ip_prefix': '0.0.0.0/0'}]
145 self.create_secgroup_rules(rulesets,
146 self.security_groups[-1]['id'])
147
148 def _create_qos_policy(self):
149 policy = self.os_admin.network_client.create_qos_policy(
150 name='test-policy',
151 description='test-qos-policy',
152 shared=True)
153 return policy['policy']['id']
154
nfridman55f2ee62019-12-17 03:06:12 -0500155 def _create_server_by_port(self, port=None):
156 """Launch an instance using a port interface;
157
158 In case that the given port is None, a new port is created,
159 activated and configured with inbound SSH and TCP connection.
160 """
161 # Create and activate the port that will be assign to the instance.
162 if port is None:
163 secgroup = self.create_security_group()
164 self.create_loginable_secgroup_rule(
165 secgroup_id=secgroup['id'])
166
167 secgroup_rules = [{'protocol': 'tcp',
168 'direction': 'ingress',
169 'port_range_min': self.NC_PORT,
170 'port_range_max': self.NC_PORT,
171 'remote_ip_prefix': '0.0.0.0/0'}]
172
173 self.create_secgroup_rules(secgroup_rules,
174 secgroup['id'])
175
176 port = self.create_port(self.network,
177 security_groups=[secgroup['id']])
178 self.fip = self.create_floatingip(port=port)
179
180 keypair = self.create_keypair()
181
182 server_kwargs = {
183 'flavor_ref': CONF.compute.flavor_ref,
184 'image_ref': CONF.compute.image_ref,
185 'key_name': keypair['name'],
186 'networks': [{'port': port['id']}],
187 }
188
189 server = self.create_server(**server_kwargs)
190 self.wait_for_server_active(server['server'])
191 self.check_connectivity(self.fip['floating_ip_address'],
192 CONF.validation.image_ssh_user,
193 keypair['private_key'])
194 return server, port
195
YAMAMOTO Takashia2cc2e52018-07-31 18:54:02 +0900196
197class QoSTest(QoSTestMixin, base.BaseTempestTestCase):
198 @classmethod
199 @tutils.requires_ext(extension="qos", service="network")
200 @base_api.require_qos_rule_type(qos_consts.RULE_TYPE_BANDWIDTH_LIMIT)
201 def resource_setup(cls):
202 super(QoSTest, cls).resource_setup()
203
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300204 @decorators.idempotent_id('00682a0c-b72e-11e8-b81e-8c16450ea513')
205 def test_qos_basic_and_update(self):
206 """This test covers both:
Itzik Brown1ef813a2016-06-06 12:56:21 +0000207
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300208 1) Basic QoS functionality
209 This is a basic test that check that a QoS policy with
210 a bandwidth limit rule is applied correctly by sending
211 a file from the instance to the test node.
212 Then calculating the bandwidth every ~1 sec by the number of bits
213 received / elapsed time.
214
215 2) Update QoS policy
216 Administrator has the ability to update existing QoS policy,
217 this test is planned to verify that:
218 - actual BW is affected as expected after updating QoS policy.
219 Test scenario:
220 1) Associating QoS Policy with "Original_bandwidth"
221 to the test node
222 2) BW validation - by downloading file on test node.
223 ("Original_bandwidth" is expected)
224 3) Updating existing QoS Policy to a new BW value
225 "Updated_bandwidth"
226 4) BW validation - by downloading file on test node.
227 ("Updated_bandwidth" is expected)
228 Note:
229 There are two options to associate QoS policy to VM:
230 "Neutron Port" or "Network", in this test
231 both options are covered.
Itzik Brown1ef813a2016-06-06 12:56:21 +0000232 """
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300233
234 # Setup resources
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800235 self._test_basic_resources()
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800236 ssh_client = self._create_ssh_client()
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300237
238 # Create QoS policy
239 bw_limit_policy_id = self._create_qos_policy()
240
241 # As admin user create QoS rule
242 rule_id = self.os_admin.network_client.create_bandwidth_limit_rule(
243 policy_id=bw_limit_policy_id,
244 max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND,
245 max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND)[
246 'bandwidth_limit_rule']['id']
247
248 # Associate QoS to the network
249 self.os_admin.network_client.update_network(
250 self.network['id'], qos_policy_id=bw_limit_policy_id)
251
252 # Create file on VM
Itzik Brown1ef813a2016-06-06 12:56:21 +0000253 self._create_file_for_bw_tests(ssh_client)
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300254
255 # 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),
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300261 timeout=self.FILE_DOWNLOAD_TIMEOUT,
262 sleep=1)
263
264 # As admin user update QoS rule
265 self.os_admin.network_client.update_bandwidth_limit_rule(
266 bw_limit_policy_id,
267 rule_id,
268 max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND * 2,
269 max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND * 2)
270
271 # Check that actual BW while downloading file
272 # is as expected (Update BW)
273 utils.wait_until_true(lambda: self._check_bw(
274 ssh_client,
275 self.fip['floating_ip_address'],
276 port=self.NC_PORT,
277 expected_bw=QoSTest.LIMIT_BYTES_SEC * 2),
278 timeout=self.FILE_DOWNLOAD_TIMEOUT,
279 sleep=1)
280
281 # Create a new QoS policy
282 bw_limit_policy_id_new = self._create_qos_policy()
283
284 # As admin user create a new QoS rule
285 rule_id_new = self.os_admin.network_client.create_bandwidth_limit_rule(
286 policy_id=bw_limit_policy_id_new,
287 max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND,
288 max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND)[
289 'bandwidth_limit_rule']['id']
290
291 # Associate a new QoS policy to Neutron port
292 self.os_admin.network_client.update_port(
293 self.port['id'], qos_policy_id=bw_limit_policy_id_new)
294
295 # Check that actual BW while downloading file
296 # is as expected (Original BW)
297 utils.wait_until_true(lambda: self._check_bw(
298 ssh_client,
299 self.fip['floating_ip_address'],
300 port=self.NC_PORT),
301 timeout=self.FILE_DOWNLOAD_TIMEOUT,
302 sleep=1)
303
304 # As admin user update QoS rule
305 self.os_admin.network_client.update_bandwidth_limit_rule(
306 bw_limit_policy_id_new,
307 rule_id_new,
308 max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND * 3,
309 max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND * 3)
310
311 # Check that actual BW while downloading file
312 # is as expected (Update BW)
313 utils.wait_until_true(lambda: self._check_bw(
314 ssh_client,
315 self.fip['floating_ip_address'],
316 port=self.NC_PORT, expected_bw=QoSTest.LIMIT_BYTES_SEC * 3),
317 timeout=self.FILE_DOWNLOAD_TIMEOUT,
Itzik Brown1ef813a2016-06-06 12:56:21 +0000318 sleep=1)
nfridman55f2ee62019-12-17 03:06:12 -0500319
320 @decorators.idempotent_id('66e5673e-0522-11ea-8d71-362b9e155667')
321 def test_attach_previously_used_port_to_new_instance(self):
322 """The test spawns new instance using port with QoS policy.
323
324        Ports with attached QoS policy could be used multiple times.
325        The policy rules have to be enforced on the new machines.
326 """
327 self.network = self.create_network()
328 self.subnet = self.create_subnet(self.network)
329 self.router = self.create_router_by_client()
330 self.create_router_interface(self.router['id'], self.subnet['id'])
331
332 vm, vm_port = self._create_server_by_port()
333
334 port_policy = self.os_admin.network_client.create_qos_policy(
335 name='port-policy',
336 description='policy for attach',
337 shared=False)['policy']
338
339 rule = self.os_admin.network_client.create_bandwidth_limit_rule(
340 policy_id=port_policy['id'],
341 max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND,
342 max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND)[
343 'bandwidth_limit_rule']
344
345 self.os_admin.network_client.update_port(
346 vm_port['id'], qos_policy_id=port_policy['id'])
347
348 self.os_primary.servers_client.delete_server(vm['server']['id'])
349 waiters.wait_for_server_termination(
350 self.os_primary.servers_client,
351 vm['server']['id'])
352
353 # Launch a new server using the same port with attached policy
354 self._create_server_by_port(port=vm_port)
355
356 retrieved_port = self.os_admin.network_client.show_port(
357 vm_port['id'])
358 self.assertEqual(port_policy['id'],
359 retrieved_port['port']['qos_policy_id'],
360 """The expected policy ID is {0},
361 the actual value is {1}""".
362 format(port_policy['id'],
363 retrieved_port['port']['qos_policy_id']))
364
365 retrieved_policy = self.os_admin.network_client.show_qos_policy(
366 retrieved_port['port']['qos_policy_id'])
367
368 retrieved_rule_id = retrieved_policy['policy']['rules'][0]['id']
369 self.assertEqual(rule['id'],
370 retrieved_rule_id,
371 """The expected rule ID is {0},
372 the actual value is {1}""".
373 format(rule['id'], retrieved_rule_id))