blob: bc94cbf23c7f21005e975a9ff2b407e5e43af0fb [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)
Maciej Józefczyk41b80192020-03-03 17:10:57 +010084 cmd = ("(nc -ll -p %d < /dev/zero > /dev/null &)" % port)
Rodolfo Alonso Hernandezaa65dfb2019-09-18 11:30:04 +000085 ssh_client.exec_command(cmd, timeout=5)
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)
141 return policy['policy']['id']
142
nfridman55f2ee62019-12-17 03:06:12 -0500143 def _create_server_by_port(self, port=None):
144 """Launch an instance using a port interface;
145
146 In case that the given port is None, a new port is created,
147 activated and configured with inbound SSH and TCP connection.
148 """
149 # Create and activate the port that will be assign to the instance.
150 if port is None:
151 secgroup = self.create_security_group()
152 self.create_loginable_secgroup_rule(
153 secgroup_id=secgroup['id'])
154
155 secgroup_rules = [{'protocol': 'tcp',
156 'direction': 'ingress',
157 'port_range_min': self.NC_PORT,
158 'port_range_max': self.NC_PORT,
159 'remote_ip_prefix': '0.0.0.0/0'}]
160
161 self.create_secgroup_rules(secgroup_rules,
162 secgroup['id'])
163
164 port = self.create_port(self.network,
165 security_groups=[secgroup['id']])
166 self.fip = self.create_floatingip(port=port)
167
168 keypair = self.create_keypair()
169
170 server_kwargs = {
171 'flavor_ref': CONF.compute.flavor_ref,
172 'image_ref': CONF.compute.image_ref,
173 'key_name': keypair['name'],
174 'networks': [{'port': port['id']}],
175 }
176
177 server = self.create_server(**server_kwargs)
178 self.wait_for_server_active(server['server'])
179 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):
194 """This test covers both:
Itzik Brown1ef813a2016-06-06 12:56:21 +0000195
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300196 1) Basic QoS functionality
197 This is a basic test that check that a QoS policy with
198 a bandwidth limit rule is applied correctly by sending
199 a file from the instance to the test node.
200 Then calculating the bandwidth every ~1 sec by the number of bits
201 received / elapsed time.
202
203 2) Update QoS policy
204 Administrator has the ability to update existing QoS policy,
205 this test is planned to verify that:
206 - actual BW is affected as expected after updating QoS policy.
207 Test scenario:
208 1) Associating QoS Policy with "Original_bandwidth"
209 to the test node
210 2) BW validation - by downloading file on test node.
211 ("Original_bandwidth" is expected)
212 3) Updating existing QoS Policy to a new BW value
213 "Updated_bandwidth"
214 4) BW validation - by downloading file on test node.
215 ("Updated_bandwidth" is expected)
216 Note:
217 There are two options to associate QoS policy to VM:
218 "Neutron Port" or "Network", in this test
219 both options are covered.
Itzik Brown1ef813a2016-06-06 12:56:21 +0000220 """
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300221
222 # Setup resources
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800223 self._test_basic_resources()
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800224 ssh_client = self._create_ssh_client()
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300225
226 # Create QoS policy
227 bw_limit_policy_id = self._create_qos_policy()
228
229 # As admin user create QoS rule
230 rule_id = self.os_admin.network_client.create_bandwidth_limit_rule(
231 policy_id=bw_limit_policy_id,
232 max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND,
233 max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND)[
234 'bandwidth_limit_rule']['id']
235
236 # Associate QoS to the network
237 self.os_admin.network_client.update_network(
238 self.network['id'], qos_policy_id=bw_limit_policy_id)
239
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300240 # Basic test, Check that actual BW while downloading file
241 # is as expected (Original BW)
Itzik Brown1ef813a2016-06-06 12:56:21 +0000242 utils.wait_until_true(lambda: self._check_bw(
243 ssh_client,
244 self.fip['floating_ip_address'],
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800245 port=self.NC_PORT),
Maciej Józefczyk41b80192020-03-03 17:10:57 +0100246 timeout=self.CHECK_TIMEOUT,
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300247 sleep=1)
248
249 # As admin user update QoS rule
250 self.os_admin.network_client.update_bandwidth_limit_rule(
251 bw_limit_policy_id,
252 rule_id,
253 max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND * 2,
254 max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND * 2)
255
256 # Check that actual BW while downloading file
257 # is as expected (Update BW)
258 utils.wait_until_true(lambda: self._check_bw(
259 ssh_client,
260 self.fip['floating_ip_address'],
261 port=self.NC_PORT,
262 expected_bw=QoSTest.LIMIT_BYTES_SEC * 2),
Maciej Józefczyk41b80192020-03-03 17:10:57 +0100263 timeout=self.CHECK_TIMEOUT,
Arkady Shtempler3e1d8f12018-08-19 10:36:24 +0300264 sleep=1)
265
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 Shtempler3e1d8f12018-08-19 10:36:24 +0300287 sleep=1)
288
289 # As admin user update QoS rule
290 self.os_admin.network_client.update_bandwidth_limit_rule(
291 bw_limit_policy_id_new,
292 rule_id_new,
293 max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND * 3,
294 max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND * 3)
295
296 # Check that actual BW while downloading file
297 # is as expected (Update BW)
298 utils.wait_until_true(lambda: self._check_bw(
299 ssh_client,
300 self.fip['floating_ip_address'],
Maciej Józefczyk41b80192020-03-03 17:10:57 +0100301 port=self.NC_PORT,
302 expected_bw=QoSTest.LIMIT_BYTES_SEC * 3),
303 timeout=self.CHECK_TIMEOUT,
Itzik Brown1ef813a2016-06-06 12:56:21 +0000304 sleep=1)
nfridman55f2ee62019-12-17 03:06:12 -0500305
306 @decorators.idempotent_id('66e5673e-0522-11ea-8d71-362b9e155667')
307 def test_attach_previously_used_port_to_new_instance(self):
308 """The test spawns new instance using port with QoS policy.
309
310        Ports with attached QoS policy could be used multiple times.
311        The policy rules have to be enforced on the new machines.
312 """
313 self.network = self.create_network()
314 self.subnet = self.create_subnet(self.network)
315 self.router = self.create_router_by_client()
316 self.create_router_interface(self.router['id'], self.subnet['id'])
317
318 vm, vm_port = self._create_server_by_port()
319
320 port_policy = self.os_admin.network_client.create_qos_policy(
321 name='port-policy',
322 description='policy for attach',
323 shared=False)['policy']
324
325 rule = self.os_admin.network_client.create_bandwidth_limit_rule(
326 policy_id=port_policy['id'],
327 max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND,
328 max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND)[
329 'bandwidth_limit_rule']
330
331 self.os_admin.network_client.update_port(
332 vm_port['id'], qos_policy_id=port_policy['id'])
333
334 self.os_primary.servers_client.delete_server(vm['server']['id'])
335 waiters.wait_for_server_termination(
336 self.os_primary.servers_client,
337 vm['server']['id'])
338
339 # Launch a new server using the same port with attached policy
340 self._create_server_by_port(port=vm_port)
341
342 retrieved_port = self.os_admin.network_client.show_port(
343 vm_port['id'])
344 self.assertEqual(port_policy['id'],
345 retrieved_port['port']['qos_policy_id'],
346 """The expected policy ID is {0},
347 the actual value is {1}""".
348 format(port_policy['id'],
349 retrieved_port['port']['qos_policy_id']))
350
351 retrieved_policy = self.os_admin.network_client.show_qos_policy(
352 retrieved_port['port']['qos_policy_id'])
353
354 retrieved_rule_id = retrieved_policy['policy']['rules'][0]['id']
355 self.assertEqual(rule['id'],
356 retrieved_rule_id,
357 """The expected rule ID is {0},
358 the actual value is {1}""".
359 format(rule['id'], retrieved_rule_id))